package de.fuberlin.projectci.lrparser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import de.fuberlin.commons.lexer.IToken;
import de.fuberlin.commons.parser.ISymbol;
import de.fuberlin.commons.parser.ISyntaxTree;
import de.fuberlin.commons.util.LogFactory;
import de.fuberlin.projectci.grammar.Grammar;
import de.fuberlin.projectci.grammar.Symbol;
import de.fuberlin.projectci.grammar.TerminalSymbol;
/**
* Repräsentiert den Knoten eines Syntaxbaums und realisiert {@link ISyntaxTree}
*
*/
public class SyntaxTreeNode implements ISyntaxTree{
private static Logger logger = LogFactory.getLogger(SyntaxTreeNode.class);
// Das [[Non]Terminal]Symbol
private Symbol symbol;
private IToken token;
// Attribute
private Map<String, Object> attributeName2Value = new HashMap<String, Object>();
private ISyntaxTree parent=null;
// cildren als LinkedList, um insertTree effizient zu implementieren zu können
private List<ISyntaxTree> children=new LinkedList<ISyntaxTree>();
/**
* Erzeugt einen inneren Knoten für das übergebene (Nichtterminal-)Symbol.
* @param symbol (Nichtterminal-)Symbol
*/
public SyntaxTreeNode(Symbol symbol) {
this.symbol = symbol;
}
/**
* Erzeugt ein Blatt im SyntaxTree für ein Terminalsymbol mit zugehörigen Eingabetoken.
* @param token Eingabetoken
* @param symbol Terminalsymbol
*/
public SyntaxTreeNode(IToken token, TerminalSymbol symbol) {
this.symbol = symbol;
this.token=token;
}
// ****************************************************************************
// * Implementierung von ISyntaxTree
// ****************************************************************************
@Override
public void addChild(ISyntaxTree tree) {
if (tree.getParent()!=null && ! this.equals(tree.getParent())){
throw new IllegalStateException("You don't have to add a child with another parent node");
}
if (children.contains(tree)){
logger.warning("Refused to re-add an existing child node.");
return;
}
children.add(tree);
tree.setParent(this);
}
@Override
public int getChildrenCount() {
return children.size();
}
@Override
public ISyntaxTree getChild(int i) {
return children.get(i);
}
@Override
public List<ISyntaxTree> getChildrenByName(String name) {
List<ISyntaxTree> result=new ArrayList<ISyntaxTree>();
for (ISyntaxTree aChildTree : children) {
if (aChildTree.getToken() != null){
if (name.equals(aChildTree.getToken().getText())){
result.add(aChildTree);
}
}
}
return result;
}
@Override
public boolean setAttribute(String name, Object value) {
attributeName2Value.put(name, value);
return true;
}
@Override
public Object getAttribute(String name) {
return attributeName2Value.get(name);
}
@Override
public boolean addAttribute(String name) {
attributeName2Value.put(name, null);
return true;
}
@Override
public void setParent(ISyntaxTree tree) {
if (this.getParent()!=null && !this.getParent().equals(tree)){
throw new IllegalStateException("You don't have to set another parent node!");
}
tree.addChild(this);
}
@Override
public ISyntaxTree getParent() {
return parent;
}
@Override
public ISyntaxTree removeChild(int i) {
return children.remove(i);
}
@Override
public IToken getToken() {
return token;
}
@Override
public List<ISyntaxTree> getChildren() {
return children;
}
@Override
public void printTree() {
System.out.println(toString());
}
@Override
public ISymbol getSymbol() {
return this.symbol;
}
// ****************************************************************************
// * Erweiterungen
// ****************************************************************************
/**
* Fügt einen SyntaxTree als erstes Kind hinzu.
* @param tree
*/
void insertTree(ISyntaxTree tree) {
children.add(0, tree);
}
/**
* Entfernt einen Kindknoten.
* @param childNode
*/
void removeChildNode(ISyntaxTree childNode){
children.remove(childNode);
}
@Override
public String toString() {
return toXML();
}
/**
* 2 Knoten sind gleich, wenn sie das gleiche Symbol und die gleichen Kinder haben.
* TODO Attribute berücksichtigen --> ... und wenn sie die gleichen Attribute mit den gleichen Werten haben.
* TODO hashCode implementieren - oder Vergleiche als Comparators implementieren.
*/
@Override
public boolean equals(Object other) {
if (other==null || !(other instanceof SyntaxTreeNode)){
return false;
}
SyntaxTreeNode otherTree=(SyntaxTreeNode) other;
if (!this.symbol.equals(otherTree.symbol)){
return false;
}
if (this.children.size()!=otherTree.children.size()){
return false;
}
for (int i = 0; i < children.size(); i++) {
ISyntaxTree thisChild=children.get(i);
ISyntaxTree otherChild=otherTree.children.get(i);
if (!thisChild.equals(otherChild)){
return false;
}
}
return true;
}
/**
* Reduziert den Syntaxbaum auf einen Abstrakten Syntaxbaum durch rekursives Hochziehen aller Einzelkinder.
*/
public void reduceToAbstractSyntaxTree(){
// Erstmal alle ε-Knoten entfernen
for (ISyntaxTree anEmptyChildNode : getChildrenByName(Grammar.EMPTY_STRING)) {
removeChildNode(anEmptyChildNode);
}
for (int i = 0; i < getChildrenCount(); i++) {
SyntaxTreeNode aChildTree=(SyntaxTreeNode) getChild(i);
aChildTree.reduceToAbstractSyntaxTree(); // Bottom-Up
if (aChildTree.getChildrenCount()==1){
// Ersetze childTree durch dessen erstes (und einziges) Kind.
aChildTree=(SyntaxTreeNode) aChildTree.getChild(0);
children.set(i, aChildTree);
}
// TODO Der Parsebaum enthält noch Nichtterminal-Blätter, die entfernt werden können.
// Der erste Ansatz funktioniert aber nicht...
// if (aChildTree.getChildrenCount()==0 && aChildTree.symbol instanceof NonTerminalSymbol){
// removeChildNode(aChildTree);
// continue;
// }
}
}
/**
* Entfernt alle Epsilon-Knoten aus dem Parsebaum.
*/
public void removeAllEpsilonNodes(){
// Erstmal alle ε-Knoten entfernen
for (ISyntaxTree anEmptyChildNode : getChildrenByName(Grammar.EMPTY_STRING)) {
removeChildNode(anEmptyChildNode);
}
for (int i = 0; i < getChildrenCount(); i++) {
SyntaxTreeNode aChildTree=(SyntaxTreeNode) getChild(i);
aChildTree.removeAllEpsilonNodes();
}
}
/**
* Erzeugt eine XML-Repräsentation des Parsebaums.
* @return
*/
public String toXML(){
StringBuffer strBuf= new StringBuffer();
strBuf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
toXML(strBuf, 0);
return strBuf.toString();
}
private void toXML(StringBuffer strBuf, int level){
for (int i = 0; i < level; i++) {
strBuf.append(" ");
}
strBuf.append("<node symbol=\"");
strBuf.append(escape(symbol.getName()));
strBuf.append("\"");
if (token!=null){
strBuf.append(" type=\"");
strBuf.append(token.getType());
strBuf.append("\"");
if (token.getAttribute()!=null){
strBuf.append(" value=\"");
strBuf.append(escape(token.getAttribute().toString()));
strBuf.append("\"");
}
}
if (children.size()>0){
strBuf.append(">\n");
for (ISyntaxTree aChildNode : children) {
((SyntaxTreeNode)aChildNode).toXML(strBuf, level+1);
}
for (int i = 0; i < level; i++) {
strBuf.append(" ");
}
strBuf.append("</node>\n");
}
else{
strBuf.append("/>\n");
}
}
/**
* Ersetzt Zeichen, die nicht in einem XML-Attribut erlaubt sind durch die zugehörige HTML-Entity.
* @param s
* @return
*/
private String escape( String s){
if (s.contains("<")){
s=s.replaceAll("<", "<");
}
if (s.contains(">")){
s=s.replaceAll("<", ">");
}
if (s.contains("\"")){
s=s.replaceAll("\"", """);
}
return s;
}
}