/* * 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.Collection; import java.util.LinkedList; import java.util.Map; import java.util.HashMap; 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.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.1 $ */ 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; /** 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).trueCondition != null ) { // 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).trueCondition != null ) { 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.map.containsKey(ident)) { Entry e = scope.symtab.map.get(ident); // Set the flags for typedef (2) and var (1). int flags = 0; if (null != e.trueCondition) { PresenceCondition and; and = e.trueCondition.and(presenceCondition); if (! and.isFalse()) { flags |= 2; } and.delRef(); } if (null != e.falseCondition) { PresenceCondition and; and = e.falseCondition.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); 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(); } } /** * 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.map.containsKey(ident)) { Entry e; boolean typedef; boolean var; PresenceCondition and; e = scope.symtab.map.get(ident); and = e.trueCondition.and(presenceCondition); if (! and.isFalse()) { return and; } and.delRef(); } 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. */ private static class SymbolTable { /** The symbol table data structure. */ public HashMap<String, Entry> map; /** The reference count for cleaning up the table BDDs */ public int refs; /** New table */ public SymbolTable() { this.map = new HashMap<String, Entry>(); this.refs = 1; } public SymbolTable addRef() { refs++; return this; } public void delRef() { refs--; if (0 == refs) { //clean up symbol table for (String str : this.map.keySet()) { Entry e = this.map.get(str); if (null != e.trueCondition) { e.trueCondition.delRef(); } if (null != e.falseCondition) { e.falseCondition.delRef(); } } } } public void add(String ident, boolean typedef, PresenceCondition presenceCondition) { if (! map.containsKey(ident)) { map.put(ident, new Entry(typedef ? presenceCondition : null, typedef ? null : presenceCondition)); presenceCondition.addRef(); } else { Entry e; e = map.get(ident); if (typedef) { if (null == e.trueCondition) { e.trueCondition = presenceCondition; presenceCondition.addRef(); } else { PresenceCondition or; or = e.trueCondition.or(presenceCondition); e.trueCondition.delRef(); e.trueCondition = or; } } else { if (null == e.falseCondition) { e.falseCondition = presenceCondition; presenceCondition.addRef(); } else { PresenceCondition or; or = e.falseCondition.or(presenceCondition); e.falseCondition.delRef(); e.falseCondition = or; } } } } public void addAll(SymbolTable symtab) { for (String str : symtab.map.keySet()) { if (! map.containsKey(str)) { Entry e = symtab.map.get(str); map.put(str, new Entry(e.trueCondition, e.falseCondition)); if (null != e.trueCondition) { e.trueCondition.addRef(); } if (null != e.falseCondition) { e.falseCondition.addRef(); } } else { Entry d = map.get(str); Entry e = symtab.map.get(str); if (null != e.trueCondition) { if (null == d.trueCondition) { d.trueCondition = e.trueCondition; e.trueCondition.addRef(); } else { PresenceCondition or; or = d.trueCondition.or(e.trueCondition); d.trueCondition.delRef(); d.trueCondition = or; } } if (null != e.falseCondition) { if (null == d.falseCondition) { d.falseCondition = e.falseCondition; e.falseCondition.addRef(); } else { PresenceCondition or; or = d.falseCondition.or(e.falseCondition); d.falseCondition.delRef(); d.falseCondition = or; } } } } } } /** An entry in the symbol table. */ private static class Entry { /** The presence condition when the symbol is a typedef name. */ PresenceCondition trueCondition; /** The presence condition when the symbol is a var name. */ PresenceCondition falseCondition; /** Create a new entry. * * @param t The typedef name presence condition. * @param f The var name presence condition. */ public Entry(PresenceCondition trueCondition, PresenceCondition falseCondition) { this.trueCondition = trueCondition; this.falseCondition = falseCondition; } } }