/** * Copyright 2004-2016 Riccardo Solmi. All rights reserved. * This file is part of the Whole Platform. * * The Whole Platform is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Whole Platform 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Whole Platform. If not, see <http://www.gnu.org/licenses/>. */ package org.whole.lang.grammars.visitors; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.whole.lang.bindings.IBindingManager; import org.whole.lang.grammars.model.As; import org.whole.lang.grammars.model.BySize; import org.whole.lang.grammars.model.Choose; import org.whole.lang.grammars.model.Concatenate; import org.whole.lang.grammars.model.DataTerminal; import org.whole.lang.grammars.model.Empty; import org.whole.lang.grammars.model.Format; import org.whole.lang.grammars.model.Grammar; import org.whole.lang.grammars.model.Indent; import org.whole.lang.grammars.model.Literal; import org.whole.lang.grammars.model.LiteralTerminal; import org.whole.lang.grammars.model.Name; import org.whole.lang.grammars.model.NewLine; import org.whole.lang.grammars.model.NonTerminal; import org.whole.lang.grammars.model.Optional; import org.whole.lang.grammars.model.Production; import org.whole.lang.grammars.model.Repeat; import org.whole.lang.grammars.model.Rule; import org.whole.lang.grammars.model.Space; import org.whole.lang.grammars.model.Split; import org.whole.lang.grammars.model.Splitter; import org.whole.lang.grammars.model.When; import org.whole.lang.grammars.reflect.GrammarsEntityDescriptorEnum; import org.whole.lang.iterators.IteratorFactory; import org.whole.lang.iterators.ScannerIterator; import org.whole.lang.matchers.GenericMatcherFactory; import org.whole.lang.matchers.Matcher; import org.whole.lang.model.IEntity; import org.whole.lang.model.adapters.IEntityAdapter; import org.whole.lang.operations.PrettyPrinterOperation; import org.whole.lang.reflect.EntityDescriptorEnum; import org.whole.lang.reflect.FeatureDescriptorEnum; import org.whole.lang.util.BehaviorUtils; import org.whole.lang.util.DataTypeUtils; import org.whole.lang.util.EntityUtils; import org.whole.lang.visitors.AbstractVisitor; import org.whole.lang.visitors.GenericTraversalFactory; import org.whole.lang.visitors.IVisitor; import org.whole.lang.visitors.VisitException; /** * @author Riccardo Solmi, Enrico Persiani */ public class GrammarBasedUnparserVisitor extends GrammarsTraverseAllVisitor { protected IEntity model; private FeatureDescriptorEnum fdEnum; private Appendable appendable; private Map<String, Production> nameProductionMap = new HashMap<String, Production>(); private Set<String> lexiconSet = new HashSet<String>(); private String name; private String delimiter = ""; private boolean delimited = false; private String space = " "; private String newLine = "\n"; private String indent = " "; private StringBuilder indentation = new StringBuilder(); private boolean isNewLine = true; protected GrammarBasedUnparserVisitor(IEntity model, IBindingManager bindingManager) { this.model = model; this.fdEnum = model.wGetEntityDescriptor().getFeatureDescriptorEnum(); setBindings(bindingManager); } public GrammarBasedUnparserVisitor(IEntity model, Appendable appendable, IBindingManager bindingManager) { this(model, bindingManager); this.appendable = appendable; } protected String getAsString(IEntity entity) { return DataTypeUtils.getAsPersistenceString(entity); } protected void appendDelimiter() { delimited = true; append(delimiter); } protected void append(CharSequence csq) { final int csqLength = csq.length(); if (csqLength == 0) return; try { if (isNewLine) appendable.append(indentation); appendable.append(csq); } catch (IOException e) { throw new IllegalStateException("cannot append characters", e); } final int newLineLength = newLine.length(); isNewLine = csqLength >= newLineLength && newLine.equals(csq.subSequence(csqLength-newLineLength, csqLength)); } protected boolean appendModelEntityAsFragment() { if (!EntityUtils.isFragment(model)) return false; IEntity result = evaluateFragment(model); String prettyPrintString = PrettyPrinterOperation.toPrettyPrintString(EntityUtils.getFragmentRoot(result)); prettyPrintString = prettyPrintString.replace(System.getProperty("line.separator"), newLine); append(prettyPrintString); return true; } protected IEntity evaluateFragment(IEntity fragment) { return BehaviorUtils.evaluate(fragment, +1, getBindings()); } protected void normalize(Grammar entity) { Grammar g = entity;//FIXME ensure normalized: NormalizerOperation.normalize(entity); ScannerIterator<Production> li = IteratorFactory.<Production>childScannerIterator(); li.reset(g.getLexicalStructure()); for (Production p : li) { String name = p.getName().getValue(); nameProductionMap.put(name, p); lexiconSet.add(name); } ScannerIterator<Production> pi = IteratorFactory.<Production>childScannerIterator(); pi.reset(g.getPhraseStructure()); for (Production p : pi) nameProductionMap.put(p.getName().getValue(), p); } protected final boolean isLexicon(NonTerminal nt) { return isLexicon(nt.getValue()); } protected final boolean isLexicon(String nt) { return lexiconSet.contains(nt); } public void unparse(IEntity model) { this.model = model; } @Override public boolean visitAdapter(IEntityAdapter entity) { //FIXME workaround for null operation if (getOperation() == null && EntityUtils.isResolver(entity)) return false; if (EntityUtils.isFragment(entity)) return true; else return super.visitAdapter(entity); } @Override public void visit(Grammar entity) { LiteralTerminal lt = entity.getDelimiter(); if (EntityUtils.isNotResolver(lt)) delimiter = lt.getLiteral().getValue(); space = EntityUtils.safeStringValue(entity.getSpaceLiteral(), space); indent = EntityUtils.safeStringValue(entity.getIndentLiteral(), indent); newLine = EntityUtils.safeStringValue(entity.getNewLineLiteral(), newLine); normalize(entity); if (!appendModelEntityAsFragment()) nameProductionMap.get(model.wGetEntityDescriptor().getName()).accept(this); if (delimited) appendDelimiter(); } @Override public void visit(Production entity) { int indentationLength = indentation.length(); entity.getRule().accept(this); if (!isLexicon(entity.getName())) indentation.setLength(indentationLength); } @Override public void visit(Concatenate entity) { if (!appendModelEntityAsFragment()) for (int i = 0; i < entity.wSize(); i++) ((Rule) entity.wGet(i)).accept(this); } @Override public void visit(Repeat entity) { if (!appendModelEntityAsFragment()) { IEntity parentEntity = model; for (int i=0; i<parentEntity.wSize(); i++) { model = parentEntity.wGet(i); if (i>0) entity.getSeparator().accept(this); entity.getRule().accept(this); } model = parentEntity; } } @Override public void visit(Optional entity) { As as = Matcher.find(GrammarsEntityDescriptorEnum.As, entity, false); if (as == null || (as != null && !EntityUtils.isResolver(model.wGet(fdEnum.valueOf(as.getName().getValue()))))) entity.getRule().accept(this); } @Override public void visit(When entity) { entity.getRule().accept(this); } private IVisitor phraseNonTerminal = GenericTraversalFactory.instance.all( GenericMatcherFactory.instance.hasTypeMatcher(GrammarsEntityDescriptorEnum.NonTerminal), new AbstractVisitor() { public void visit(IEntity entity) { if (isLexicon((NonTerminal) entity)) throw new VisitException(); } }); @Override public void visit(Choose entity) { if (!appendModelEntityAsFragment()) { if (Matcher.match(GrammarsEntityDescriptorEnum.As, entity.wGet(0))) append(getAsString(model)); else { // get rule using model entity EntityDescriptorEnum edEnum = model.wGetLanguageKit().getEntityDescriptorEnum(); Rule rule = null; for (int size=entity.wSize(), i=0; rule == null && i<size; i++) { Rule child = (Rule) entity.wGet(i); NonTerminal nt = (NonTerminal) Matcher.find(phraseNonTerminal, child, false); if (Matcher.isAssignableAsIsFrom(edEnum.valueOf(nt.getValue()), model))//TODO test was isAssignableFrom rule = child; } if (rule != null) rule.accept(this); else throw new IllegalStateException("missing choose rule for model entity"); } } } @Override public void visit(As entity) { entity.getName().accept(this);//get the feature name IEntity parentEntity = model; model = model.wGet(fdEnum.valueOf(name)); entity.getRule().accept(this); model = parentEntity; } @Override public void visit(NonTerminal entity) { nameProductionMap.get(entity.getValue()).accept(this); } @Override public void visit(Name entity) { name = entity.getValue(); } @Override public void visit(Empty entity) { entity.getLiteral().accept(this); } @Override public void visit(Space entity) { append(space); } @Override public void visit(Indent entity) { if (isNewLine) indentation.append(indent); else append(indent); } @Override public void visit(NewLine entity) { append(newLine); } @Override public void visit(LiteralTerminal entity) { appendDelimiter(); entity.getLiteral().accept(this); } @Override public void visit(DataTerminal entity) { appendDelimiter(); if (!appendModelEntityAsFragment()) entity.getFormat().accept(this); } @Override public void visit(Format entity) { append(getAsString(model)); } @Override public void visit(Literal entity) { IEntity result; if (EntityUtils.isFragment(entity)) { IBindingManager bindings = getBindings(); bindings.wEnterScope(); bindings.wDef("self", model); result = BehaviorUtils.evaluate(entity, 0, bindings); bindings.wExitScope(); } else result = entity; append(result.wStringValue()); } public void visit(Split entity) { Splitter splitter = entity.getSplitter(); //FIXME workaround if (Matcher.matchImpl(GrammarsEntityDescriptorEnum.BySize, splitter)) { // use a temporary appendable Appendable oldAppendable = appendable; StringBuilder sb; appendable = sb = new StringBuilder(); entity.getRule().accept(this); appendable = oldAppendable; // pad the result buffer int size = ((BySize) splitter).getValue(); for (int i=sb.length(); i<size; i++) sb.append(' '); append(sb.toString()); } else super.visit(entity); } }