/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.parsing.bnf.nodes; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import com.aptana.ide.io.SourceWriter; import com.aptana.ide.lexer.DynamicEnumerationMap; import com.aptana.ide.lexer.IEnumerationMap; import com.aptana.ide.parsing.bnf.Item; import com.aptana.ide.parsing.bnf.TerminalList; /** * @author Kevin Lindsey */ public class GrammarNode extends GrammarNodeBase { /** * @author Kevin Lindsey */ private static class NodeKey { public int type; public String name; /** * @param node */ public NodeKey(IGrammarNode node) { this(node.getTypeIndex(), node.getName()); } /** * @param type * @param name */ public NodeKey(int type, String name) { this.type = type; this.name = name; } /** * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { boolean result = false; if (this == obj) { result = true; } else if (obj instanceof NodeKey) { NodeKey nodeKey = (NodeKey) obj; result = (this.type == nodeKey.type && this.name == nodeKey.name); } return result; } /** * @see java.lang.Object#hashCode() */ public int hashCode() { return this.type ^ this.name.hashCode(); } } private Map<String, List<ProductionNode>> _productions; private String _startingName; private TerminalNode _eofNode; private IEnumerationMap _terminalMap; private IEnumerationMap _nonTerminalMap; private Map<NodeKey,IGrammarNode> _nodeCache; /** * GrammarNode * * @param name */ public GrammarNode(String name) { super(null, GrammarNodeTypes.GRAMMAR, name); this._terminalMap = new DynamicEnumerationMap(); this._nonTerminalMap = new DynamicEnumerationMap(); // make sure EOF is 0 this._terminalMap.getIntValue("$"); //$NON-NLS-1$ this._nodeCache = new HashMap<NodeKey,IGrammarNode>(); } /** * @see com.aptana.ide.parsing.bnf.nodes.GrammarNodeBase#appendChild(com.aptana.ide.parsing.bnf.nodes.IGrammarNode) */ public void appendChild(IGrammarNode child) { super.appendChild(child); if (child.getTypeIndex() == GrammarNodeTypes.PRODUCTION) { ProductionNode production = (ProductionNode) child; String name = production.getName(); if (this._startingName == null) { this._startingName = production.getName(); } if (this._productions == null) { this._productions = new HashMap<String, List<ProductionNode>>(); } if (this._productions.containsKey(name) == false) { this._productions.put(name, new ArrayList<ProductionNode>()); } this._productions.get(name).add(production); } } /** * createAndNode */ public SequenceNode createSequenceNode() { return new SequenceNode(this); } /** * createEOFNode * * @return */ public TerminalNode createEOFNode() { if (this._eofNode == null) { this._eofNode = this.createTerminalNode("$"); //$NON-NLS-1$ } return this._eofNode; } /** * createEmptyNode * * @return */ public EmptyNode createEmptyNode() { return new EmptyNode(this); } /** * NonTerminalNode * * @param name */ public NonTerminalNode createNonTerminalNode(String name) { int index = this._nonTerminalMap.getIntValue(name); // NodeKey key = new NodeKey(NodeTypes.NONTERMINAL, name); // NonTerminalNode result; // // if (this._nodeCache.containsKey(key)) { // result = (NonTerminalNode) this._nodeCache.get(key); // } else { // result = new NonTerminalNode(this, name, index); // this._nodeCache.put(key, result); // } // // return result; return new NonTerminalNode(this, name, index); } /** * createProductionNode * * @param name * @return */ public ProductionNode createProductionNode(String name) { return new ProductionNode(this, name); } /** * createTerminalNode * * @param name * @return */ public TerminalNode createTerminalNode(String name) { int index = this._terminalMap.getIntValue(name); NodeKey key = new NodeKey(GrammarNodeTypes.TERMINAL, name); TerminalNode result; if (this._nodeCache.containsKey(key)) { result = (TerminalNode) this._nodeCache.get(key); } else { result = new TerminalNode(this, name, index); this._nodeCache.put(key, result); } return result; } /** * getExpandedProductions * * @return GrammarNode */ public GrammarNode getExpandedGrammar() { GrammarNode result = new GrammarNode(this.getName()); for (int i = 0; i < this.getChildCount(); i++) { IGrammarNode child = (IGrammarNode) this.getChild(i); if (child.getTypeIndex() == GrammarNodeTypes.PRODUCTION) { ProductionNode production = (ProductionNode) child; String name = production.getName(); for (int j = 0; j < production.getChildCount(); j++) { ProductionNode newProduction = result.createProductionNode(name); newProduction.appendChild(production.getChild(j)); result.appendChild(newProduction); } } } result.setStartingName(this.getStartingName()); return result; } /** * getFirst * * @param name * @return TerminalList */ public TerminalList getFirst(String name) { ProductionNode[] productions = this.getProductionsByName(name); TerminalList result = new TerminalList(); for (int i = 0; i < productions.length; i++) { ProductionNode production = productions[i]; production.getFirst(result); } return result; } /** * getFollow * * @param name * @return TerminalList */ public TerminalList getFollow(String name) { TerminalList terminals = new TerminalList(); this.getFollow(name, terminals); return terminals; } /** * getFollow * * @param name * @param terminals */ public void getFollow(String name, TerminalList terminals) { if (name.equals(this._startingName)) { terminals.add(this.createEOFNode()); } for (Item item : this.getProductionsWithNonTerminal(name)) { item.getFollow(terminals); } } /** * getProductionsWithNonTerminal * * @param name * @return */ private Item[] getProductionsWithNonTerminal(String name) { List<Item> result = new ArrayList<Item>(); for (int i = 0; i < this.getChildCount(); i++) { ProductionNode productionNode = (ProductionNode) this.getChild(i); Item item = productionNode.findNonTerminal(name); if (item != null) { result.add(item); } } return result.toArray(new Item[result.size()]); } /** * getProductionNames * * @return String[] */ public String[] getProductionNames() { List<String> names = new ArrayList<String>(); // NOTE: We want the production names in the order in which they were // added, so we traverse the child nodes instead of using the keys from // the productions Map for (int i = 0; i < this.getChildCount(); i++) { IGrammarNode child = (IGrammarNode) this.getChild(i); if (child.getTypeIndex() == GrammarNodeTypes.PRODUCTION) { ProductionNode production = (ProductionNode) child; String name = production.getName(); if (names.contains(name) == false) { names.add(name); } } } return names.toArray(new String[names.size()]); } /** * getProductions * * @param name * @return */ public ProductionNode[] getProductionsByName(String name) { List<ProductionNode> result; if (this._productions.containsKey(name)) { result = this._productions.get(name); } else { result = new ArrayList<ProductionNode>(); } return result.toArray(new ProductionNode[result.size()]); } // /** // * getSortedGrammar // * // * @return GrammarNode // */ // public GrammarNode getSortedGrammar() { // GrammarNode result = new GrammarNode(this.getName()); // // result.setStartingName(this.getStartingName()); // // return result; // } /** * getStartingProduction * * @return */ public String getStartingName() { return this._startingName; } /** * getSymbols * * @return */ public IGrammarNode[] getSymbols() { List<IGrammarNode> symbols = new ArrayList<IGrammarNode>(); // add EOF symbols.add(this.createEOFNode()); // add all descendant symbols for (int i = 0; i < this.getChildCount(); i++) { IGrammarNode child = (IGrammarNode) this.getChild(i); child.getSymbols(symbols); } GrammarNodeBase[] result = symbols.toArray(new GrammarNodeBase[symbols.size()]); Arrays.sort(result, new Comparator<GrammarNodeBase>() { public int compare(GrammarNodeBase o1, GrammarNodeBase o2) { int type1 = o1.getTypeIndex(); int type2 = o2.getTypeIndex(); int result = 0; if (type1 == type2) { if (type1 == GrammarNodeTypes.TERMINAL) { int index1 = ((TerminalNode) o1).getIndex(); int index2 = ((TerminalNode) o2).getIndex(); result = index1 - index2; } else { result = o1.getName().compareTo(o2.getName()); } } else { if (type1 == GrammarNodeTypes.TERMINAL) { result = -1; } else { result = 1; } } return result; } }); return result; } /** * setStartingName * * @param startingName */ public void setStartingName(String startingName) { this._startingName = startingName; } /** * @see com.aptana.ide.parsing.bnf.nodes.GrammarNodeBase#getSource(com.kevlindev.io.SourceWriter) */ public void getSource(SourceWriter writer) { if (this.hasChildren()) { this.getChild(0).getSource(writer); for (int i = 1; i < this.getChildCount(); i++) { writer.println(); this.getChild(i).getSource(writer); } } } }