/*
* xtc - The eXTensible Compiler
* Copyright (C) 2009-2012 New York University
*
* 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.cpp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import xtc.tree.Location;
import xtc.lang.cpp.Syntax.Kind;
import xtc.lang.cpp.Syntax.LanguageTag;
import xtc.lang.cpp.Syntax.ConditionalTag;
import xtc.lang.cpp.Syntax.DirectiveTag;
import xtc.lang.cpp.Syntax.Layout;
import xtc.lang.cpp.Syntax.Language;
import xtc.lang.cpp.Syntax.Text;
import xtc.lang.cpp.Syntax.Directive;
import xtc.lang.cpp.Syntax.Conditional;
import xtc.type.Type;
import xtc.lang.cpp.PresenceConditionManager.PresenceCondition;
import xtc.lang.cpp.ForkMergeParser.OrderedSyntax;
import xtc.lang.cpp.ForkMergeParser.Lookahead;
/**
* This class maintains just enough type context for parsing.
*
* @author Paul Gazzillo
* @version $Revision: 1.8 $
*/
public class CContext implements ParsingContext {
/** Output bindings and scope changes. */
protected static boolean DEBUG = false;
/** The symbol table for this parsing context. */
protected SymbolTable symtab;
/**
* The parent parsing context, corresponding to the parent
* scope.
*/
protected CContext parent;
/**
* Whether the scope this parsing context is associated with is
* reentrant. This is used to parse function definitions.
*/
protected boolean reentrant;
/**
* Store last seen base type from specifiers. Used to build type
* from a declarator's abstract type.
*/
public Type lastSeenType;
/** Whether to display language statistics. */
boolean languageStatistics;
/**
* A three-bit digit. This is used to capture typedef/var ambiguity
* when one token can be both.
*/
public static enum trit {
TRUE,
FALSE,
TRUEFALSE
}
/** Create a new initial C parsing contex. */
public CContext() {
this(new SymbolTable(), null);
}
/**
* Create a new C parsing context.
*
* @param symtab The symbol table for this parsing context and scope.
* @param parent The parent parsing context and scope.
*/
public CContext(SymbolTable symtab, CContext parent) {
this.symtab = symtab;
this.parent = parent;
this.reentrant = false;
}
/**
* Turn language statistics collection on. Default is off.
*
* @param b True is on.
*/
public void collectStatistics(boolean b) {
languageStatistics = b;
}
/**
* Copy a C parsing context. Used for forking the parsing context.
*
* @param scope The parsing context to copy.
*/
public CContext(CContext scope) {
this.symtab = scope.symtab.addRef();
if (scope.parent != null) {
this.parent = new CContext(scope.parent);
} else {
this.parent = null;
}
this.reentrant = scope.reentrant;
}
public ParsingContext fork() {
return new CContext(this);
}
/**
* Check whether a syntax token is an identifier.
*
* @return true if syntax is an identifier.
*/
@SuppressWarnings("unchecked")
private boolean isIdentifier(Syntax syntax) {
return syntax.kind() == Kind.LANGUAGE
&& ((Language<CTag>)syntax).toLanguage().tag() == CTag.IDENTIFIER;
}
// get around capture of ? to CTag warning
public boolean shouldReclassify(Collection<Lookahead> set) {
// Check whether any tokens need reclassification, i.e. they are
// an identifier and have an entry in the symbol.
for (Lookahead n : set) {
if (isIdentifier(n.token.syntax)) {
String ident = n.token.syntax.getTokenText();
// Check the stack of symbol tables for a typedef entry.
CContext scope = this;
while (true) {
while (scope.reentrant) scope = scope.parent;
// if ( scope.symtab.map.containsKey(ident)
// && scope.symtab.map.get(ident).typedefCond != null
// ) {
if ( scope.symtab.bools.containsKey(ident)
&& scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.TYPEDEF)
&& ! scope.symtab.bools.get(ident).get(SymbolTable.STField.TYPEDEF).trueCond.isFalse()
) {
// The identifier has a typedef entry some presence
// condition in the symbol table. Therefore the parser
// needs to call reclassify.
return true;
}
if (null == scope.parent) {
break;
}
scope = scope.parent;
}
}
}
return false;
}
// get around capture of ? to CTag warning
public Collection<Lookahead> reclassify(Collection<Lookahead> set) {
// Reclassify any tokens that are typedef names and also create a
// new token when there is a typedef/var ambiguity so the FMLR
// parser will fork.
Collection<Lookahead> newTokens = null;
for (Lookahead n : set) {
// Get the symbol table entry for the token.
trit isTypedef = trit.FALSE;
if (isIdentifier(n.token.syntax)) {
isTypedef = isType(n.token.syntax.getTokenText(),
n.presenceCondition, n.token.syntax.getLocation());
}
switch (isTypedef) {
case TRUEFALSE:
// A typedef ambiguity. Find the presence condition for both
// the variable entry and for the typedef entry. Reclassify
// the current token as a typedef name and update its presence
// condition. Then add a new token for the variable entry.
PresenceCondition typedefPresenceCondition
= this.typedefPresenceCondition(n.token.syntax.getTokenText(), n.presenceCondition);
n.presenceCondition.delRef();
n.presenceCondition = typedefPresenceCondition;
// Add a new token for the variable entry.
PresenceCondition varPresenceCondition = typedefPresenceCondition.not();
Lookahead identifier = new Lookahead(n.token, varPresenceCondition);
if (null == newTokens) {
newTokens = new LinkedList<Lookahead>();
}
newTokens.add(identifier);
// Fall through to reclassify the token as a TYPEDEFname.
case TRUE:
// A typedef name. Reclassify the token replacing the
// IDENTIFIER token with a TYPEDEFname token.
Language<CTag> newToken = new Text<CTag>(CTag.TYPEDEFname,
n.token.syntax.getTokenText());
// Copy the location.
newToken.setLocation(n.token.syntax.getLocation());
// Copy the ordering wrapper for the token.
n.token = n.token.copy(newToken);
break;
case FALSE:
// No reclassification necessary.
break;
}
}
return newTokens;
}
/**
* This method determines whether an identifier is a typedef name,
* var name, or both by inspecting the symbol table in this scope
* and any parent scopes.
*
* @param ident The identifier.
* @param presenceCondition The presence condition.
*/
public trit isType(String ident, PresenceCondition presenceCondition,
Location location) {
CContext scope;
scope = this;
while (true) {
while (scope.reentrant) scope = scope.parent;
// if ( scope.symtab.map.containsKey(ident)
// && scope.symtab.map.get(ident).typedefCond != null
// ) {
if (scope.symtab.bools.containsKey(ident)
&& scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.TYPEDEF)
&& ! scope.symtab.bools.get(ident).get(SymbolTable.STField.TYPEDEF).trueCond.isFalse()) {
break;
}
if (null == scope.parent) {
return trit.FALSE;
}
scope = scope.parent;
}
scope = this;
do { //find the symbol in local scope or parent scope
while (scope.reentrant) scope = scope.parent;
if (scope.symtab.bools.containsKey(ident) &&
(scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.TYPEDEF)
|| scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.IDENT))) {
// Set the flags for typedef (2) and var (1).
int flags = 0;
if (scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.TYPEDEF) &&
! scope.symtab.bools.get(ident).get(SymbolTable.STField.TYPEDEF).trueCond.isFalse()) {
PresenceCondition and = scope.symtab.bools.get(ident).get(SymbolTable.STField.TYPEDEF).trueCond.and(presenceCondition);
if (! and.isFalse()) flags |= 2;
and.delRef();
}
if (scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.IDENT) &&
! scope.symtab.bools.get(ident).get(SymbolTable.STField.IDENT).trueCond.isFalse()) {
PresenceCondition and = scope.symtab.bools.get(ident).get(SymbolTable.STField.IDENT).trueCond.and(presenceCondition);
if (! and.isFalse()) flags |= 1;
and.delRef();
}
switch (flags) {
case 3:
if (DEBUG) System.err.println("isType: " + ident
+ " true/false in " /*+ presenceCondition*/);
if (languageStatistics) {
System.err.println(String.format("typedef_ambiguity %s %s",
ident, location));
}
return trit.TRUEFALSE;
case 2:
if (DEBUG) System.err.println("isType: " + ident
+ " true in " /*+ presenceCondition*/);
return trit.TRUE;
case 1:
if (DEBUG) System.err.println("isType: " + ident
+ " false in " /*+ presenceCondition*/);
return trit.FALSE;
}
}
// if (scope.symtab.map.containsKey(ident)) {
// TypedefVarEntry e = scope.symtab.map.get(ident);
// // Set the flags for typedef (2) and var (1).
// int flags = 0;
// if (null != e.typedefCond) {
// PresenceCondition and;
// and = e.typedefCond.and(presenceCondition);
// if (! and.isFalse()) {
// flags |= 2;
// }
// and.delRef();
// }
// if (null != e.varCond) {
// PresenceCondition and;
// and = e.varCond.and(presenceCondition);
// if (! and.isFalse()) {
// flags |= 1;
// }
// and.delRef();
// }
// switch (flags) {
// case 3:
// if (DEBUG) System.err.println("isType: " + ident
// + " true/false in " /*+ presenceCondition*/);
// if (languageStatistics) {
// System.err.println(String.format("typedef_ambiguity %s %s",
// ident, location));
// }
// return trit.TRUEFALSE;
// case 2:
// if (DEBUG) System.err.println("isType: " + ident
// + " true in " /*+ presenceCondition*/);
// return trit.TRUE;
// case 1:
// if (DEBUG) System.err.println("isType: " + ident
// + " false in " /*+ presenceCondition*/);
// return trit.FALSE;
// }
// }
if (null == scope.parent) {
break;
}
scope = scope.parent;
} while (true);
if (DEBUG) System.err.println("isType: " + ident
+ " false in " /*+ presenceCondition*/);
return trit.FALSE;
}
public boolean mayMerge(ParsingContext other) {
if (! (other instanceof CContext)) return false;
return mergeable(this, (CContext) other);
}
/**
* A helper method for testing mergeability.
*
* @param s The first parsing context.
* @param t The second parsing context.
*/
private static boolean mergeable(CContext s, CContext t) {
if ((null == s) && (null == t)) {
return true;
} else if ((null == s) || (null == t)) {
return false;
} else if (s.symtab == t.symtab) {
return true;
} else if (s.reentrant != t.reentrant) {
return false;
} else {
return mergeable(s.parent, t.parent);
}
}
public ParsingContext merge(ParsingContext other) {
CContext scope = (CContext) other;
if (this.symtab == scope.symtab) {
return this;
} else {
// symtab.addAll(scope.symtab);
symtab.copyBools(scope.symtab);
if (null != parent) {
return parent.merge(scope.parent);
} else {
return null;
}
}
}
/** Free BDDs in the symbol table and those of the parent scopes. */
public void free() {
symtab.delRef();
if (null != parent) {
parent.free();
}
}
/**
* Get a reference to the context's symbol table.
*
* @return The context's symbol table.
*/
public SymbolTable getSymbolTable() {
CContext scope = this;
while (scope.reentrant) scope = scope.parent;
return scope.symtab;
}
// /**
// * Bind an identifier to a typedef or var name for a given presence
// * condition.
// *
// * @param ident The identifier.
// * @param typedef Whether its a typedef name or a var name.
// * @param presenceCondition The presence condition.
// */
// public void bind(String ident, boolean typedef, PresenceCondition presenceCondition) {
// CContext scope;
// if (DEBUG) {
// System.err.println("bind: " + ident + " " + typedef);
// }
// scope = this;
// while (scope.reentrant) scope = scope.parent;
// scope.symtab.add(ident, typedef, presenceCondition);
// }
/**
* Return the presence condition under which an identifier is a
* typedef name.
*
* @param ident The identifier.
* @param presenceCondition The current presence condition.
*/
public PresenceCondition typedefPresenceCondition(String ident, PresenceCondition presenceCondition) {
CContext scope;
scope = this;
do { //find the symbol in local scope or parent scope
while (scope.reentrant) scope = scope.parent;
if (scope.symtab.bools.containsKey(ident) &&
scope.symtab.bools.get(ident).containsKey(SymbolTable.STField.TYPEDEF)) {
PresenceCondition and = scope.symtab.bools.get(ident).get(SymbolTable.STField.TYPEDEF).trueCond.and(presenceCondition);
if (! and.isFalse()) {
return and;
}
and.delRef();
}
// if (scope.symtab.map.containsKey(ident)) {
// TypedefVarEntry e;
// boolean typedef;
// boolean var;
// PresenceCondition and;
// e = scope.symtab.map.get(ident);
// and = e.typedefCond.and(presenceCondition);
// if (! and.isFalse()) {
// return and;
// }
// and.delRef();
// }
if (null == scope.parent) {
break;
}
scope = scope.parent;
} while (true);
return null;
}
/**
* Return the presence condition under which an identifier is a
* typedef name.
*
* @param ident The identifier.
* @param presenceCondition The current presence condition.
*/
public PresenceCondition symbolPresenceCond(String ident, SymbolTable.STField field) {
CContext scope;
scope = this;
do { //find the symbol in local scope or parent scope
while (scope.reentrant) scope = scope.parent;
if (scope.symtab.bools.containsKey(ident) &&
scope.symtab.bools.get(ident).containsKey(field)) {
return scope.symtab.bools.get(ident).get(field).trueCond;
}
if (null == scope.parent) {
break;
}
scope = scope.parent;
} while (true);
return null;
}
/**
* Enter a new scope.
*
* @param presenceCondition The current presence condition.
* @return The parsing context of the new scope.
*/
public CContext enterScope(PresenceCondition presenceCondition) {
CContext scope;
if (DEBUG) System.err.println("enter scope");
scope = this;
while (scope.reentrant) {
scope.symtab.delRef();
scope.symtab = null;
scope = scope.parent;
}
scope = new CContext(new SymbolTable(), new CContext(scope));
return scope;
}
/**
* Exit the scope.
*
* @param presenceCondition The current presence condition.
* @return The parsing context of the parent scope.
*/
public CContext exitScope(PresenceCondition presenceCondition) {
CContext scope;
if (DEBUG) System.err.println("exit scope");
scope = this;
while (scope.reentrant) {
scope.symtab.delRef();
scope.symtab = null;
scope = scope.parent;
}
scope.symtab.delRef();
scope.symtab = null;
scope = scope.parent;
return scope;
}
/**
* Exit a reentrant scope.
*
* @param presenceCondition The current presence condition.
* @return The parsing context of the parent scope.
*/
public CContext exitReentrantScope(PresenceCondition presenceCondition) {
CContext scope;
if (DEBUG) System.err.println("exit reentrant scope");
scope = this;
while (scope.reentrant) {
scope.symtab.delRef();
scope.symtab = null;
scope = scope.parent;
}
scope.reentrant = true;
return scope;
}
/**
* Reenter a reentrant scope.
*
* @param presenceCondition The current presence condition.
* @return The parsing context of the reentered scope.
*/
public CContext reenterScope(PresenceCondition presenceCondition) {
if (DEBUG) System.err.println("reenter scope");
if (! reentrant) {
// This may happen for functions without a postfix declarator.
// See cpp_testsuite/grammar/scope.c. The parameter list
// nonterminal enters and exits a reentrant scope. Then the
// function nonterminal reenters the scope. If there is no
// list, there is no reentrant scope, and at the end of the
// function nonterminal, exitScope is called, returning null.
if (DEBUG) System.err.println("not reentrant");
return enterScope(presenceCondition);
} else {
reentrant = false;
return this;
}
}
/**
* Kill a reentrant scope.
*
* @param presenceCondition The current presence condition.
* @return The parsing context of the non-reentrant parent scope.
*/
public CContext killReentrantScope(PresenceCondition presenceCondition) {
CContext scope;
if (DEBUG) System.err.println("kill reentrant scope");
scope = this;
while (scope.reentrant) {
scope.symtab.delRef();
scope.symtab = null;
scope = scope.parent;
}
return scope;
}
/** The symbol table that stores a scope's symbol bindings. */
public static class SymbolTable {
public enum STField {
TYPEDEF, IDENT, INIT, USED, VAR, GLOBAL_FUNDEF, STATIC_FUNDEF, FUNCALL,
}
// /** The symbol table data structure. */
// public HashMap<String, TypedefVarEntry> map;
/** The symbol table data structure. */
public HashMap<String, EnumMap<STField, ConditionedBool>> bools;
/** The reference count for cleaning up the table BDDs */
public int refs;
/** New table */
public SymbolTable() {
// this.map = new HashMap<String, TypedefVarEntry>();
this.bools = new HashMap<String, EnumMap<STField, ConditionedBool>>();
this.refs = 1;
}
public SymbolTable addRef() {
refs++;
return this;
}
public void delRef() {
refs--;
if (0 == refs) { //clean up symbol table
for (String name : this.bools.keySet()) {
for (STField field : this.bools.get(name).keySet()) {
ConditionedBool cb = this.bools.get(name).get(field);
cb.trueCond.delRef();
}
}
// for (String str : this.map.keySet()) {
// TypedefVarEntry e = this.map.get(str);
// if (null != e.typedefCond) {
// e.typedefCond.delRef();
// }
// if (null != e.varCond) {
// e.varCond.delRef();
// }
// }
}
}
/**
* Set the presence condition of the boolean value of the given
* field.
*
* @param name The name of the symbol.
* @param field The field of the symbol table entry to update.
* @param value The boolean value to set.
* @param condition The condition of the boolean value.
*/
public void setbool(String name,
STField field,
boolean value,
PresenceCondition condition) {
if (! bools.containsKey(name)) {
// create a new symbol table entry
bools.put(name, new EnumMap<STField, ConditionedBool>(STField.class));
}
if (! bools.get(name).containsKey(field)) {
// create a new field for the symbol table entry
PresenceCondition trueCond;
if (value) {
trueCond = condition.addRef();
} else {
trueCond = condition.not();
}
bools.get(name).put(field, new ConditionedBool(trueCond));
} else {
// update the existing field's presence conditions
ConditionedBool cb = bools.get(name).get(field);
if (value) {
PresenceCondition union = cb.trueCond.or(condition);
cb.trueCond.delRef();
cb.trueCond = union;
} else {
PresenceCondition not = condition.not();
PresenceCondition union = cb.trueCond.and(not);
cb.trueCond.delRef();
cb.trueCond = union;
not.delRef();
}
}
}
/**
* Get all names given a field.
*
* @param field The given field.
* @return A list of names.
*/
public Set<String> getNames(STField field) {
Set<String> a = new HashSet<String>();
for (String s : bools.keySet()) {
if (bools.get(s).containsKey(field)) {
a.add(s);
}
}
return a;
}
/**
* Get the presence condition of a given name and field
* combination.
*
* @param name The symbol name.
* @param field The symbol field.
* @return The presence condition.
*/
public PresenceCondition getPresenceCond(String name,
STField field) {
if (bools.containsKey(name) &&
bools.get(name).containsKey(field)) {
return bools.get(name).get(field).trueCond;
}
return null;
}
// public void add(String ident, boolean typedef, PresenceCondition presenceCondition) {
// if (! map.containsKey(ident)) {
// map.put(ident,
// new TypedefVarEntry(typedef ? presenceCondition : null, typedef ? null : presenceCondition));
// presenceCondition.addRef();
// }
// else {
// TypedefVarEntry e;
// e = map.get(ident);
// if (typedef) {
// if (null == e.typedefCond) {
// e.typedefCond = presenceCondition;
// presenceCondition.addRef();
// }
// else {
// PresenceCondition or;
// or = e.typedefCond.or(presenceCondition);
// e.typedefCond.delRef();
// e.typedefCond = or;
// }
// }
// else {
// if (null == e.varCond) {
// e.varCond = presenceCondition;
// presenceCondition.addRef();
// }
// else {
// PresenceCondition or;
// or = e.varCond.or(presenceCondition);
// e.varCond.delRef();
// e.varCond = or;
// }
// }
// }
// }
public void copyBools(SymbolTable symtab) {
for (String name : symtab.bools.keySet()) {
for (STField field : symtab.bools.get(name).keySet()) {
this.setbool(name, field, true, symtab.bools.get(name).get(field).trueCond);
}
}
}
// public void addAll(SymbolTable symtab) {
// for (String str : symtab.map.keySet()) {
// if (! map.containsKey(str)) {
// TypedefVarEntry e = symtab.map.get(str);
// map.put(str, new TypedefVarEntry(e.typedefCond, e.varCond));
// if (null != e.typedefCond) {
// e.typedefCond.addRef();
// }
// if (null != e.varCond) {
// e.varCond.addRef();
// }
// }
// else {
// TypedefVarEntry d = map.get(str);
// TypedefVarEntry e = symtab.map.get(str);
// if (null != e.typedefCond) {
// if (null == d.typedefCond) {
// d.typedefCond = e.typedefCond;
// e.typedefCond.addRef();
// }
// else {
// PresenceCondition or;
// or = d.typedefCond.or(e.typedefCond);
// d.typedefCond.delRef();
// d.typedefCond = or;
// }
// }
// if (null != e.varCond) {
// if (null == d.varCond) {
// d.varCond = e.varCond;
// e.varCond.addRef();
// }
// else {
// PresenceCondition or;
// or = d.varCond.or(e.varCond);
// d.varCond.delRef();
// d.varCond = or;
// }
// }
// }
// }
// }
// }
// /** An entry in the symbol table. */
// private static class TypedefVarEntry {
// /** The presence condition when the symbol is a typedef name. */
// PresenceCondition typedefCond;
// /** The presence condition when the symbol is a var name. */
// PresenceCondition varCond;
// /** Create a new entry.
// *
// * @param t The typedef name presence condition.
// * @param f The var name presence condition.
// */
// public TypedefVarEntry(PresenceCondition typedefCond, PresenceCondition varCond) {
// this.typedefCond = typedefCond;
// this.varCond = varCond;
// }
}
/**
* A boolean that maintain a boolean expression for when the
* variable can be true.
*/
private static class ConditionedBool {
/** The presence condition when true. */
PresenceCondition trueCond;
/** Create a new entry.
*
* @param trueCond Condition when true.
*/
public ConditionedBool(PresenceCondition trueCond) {
this.trueCond = trueCond;
}
}
}