eu.mihosoft.vmf.vmftext.grammar.unparser.UnparserCodeGenerator Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2017-2018 Michael Hoffer . All rights reserved.
* Copyright 2017-2018 Goethe Center for Scientific Computing, University Frankfurt. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* If you use this software for scientific research then please cite the following publication(s):
*
* M. Hoffer, C. Poliwoda, & G. Wittum. (2013). Visual reflection library:
* a framework for declarative GUI programming on the Java platform.
* Computing and Visualization in Science, 2013, 16(4),
* 181–192. http://doi.org/10.1007/s00791-014-0230-y
*/
package eu.mihosoft.vmf.vmftext.grammar.unparser;
import eu.mihosoft.vmf.core.TypeUtil;
import eu.mihosoft.vmf.core.io.Resource;
import eu.mihosoft.vmf.core.io.ResourceSet;
import eu.mihosoft.vmf.vmftext.StringUtil;
import eu.mihosoft.vmf.vmftext.TemplateEngine;
import eu.mihosoft.vmf.vmftext.grammar.*;
import org.apache.velocity.VelocityContext;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class UnparserCodeGenerator {
public static void generateUnparser(GrammarModel gModel, ReadOnlyUnparserModel roModel,
String unparserGrammarPath, ResourceSet resourceSet) {
// ensure we work on our own modifiable copy of the model
UnparserModel model = roModel.asModifiable();
List rules = computeFinalUnparserRuleList(model);
try (Resource resource =
resourceSet.open(TypeUtil.computeFileNameFromJavaFQN(
gModel.getPackageName()+".unparser."+gModel.getGrammarName() + "ModelUnparser"));
Writer w = resource.open()) {
generateUPParentUnparserCode(gModel, model,rules, w);
} catch (IOException e) {
e.printStackTrace();
}
generateUPCode(gModel, model,rules, resourceSet);
try (Resource resource =
resourceSet.open(unparserGrammarPath);
Writer w = resource.open()) {
generateUPGrammarCode(gModel, model, rules, w);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void generateUPGrammarCode(GrammarModel gModel, UnparserModel model,
List rules, Writer w) throws IOException {
w.append("grammar ").append(gModel.getGrammarName()+"ModelUnparserGrammar;").append('\n');
w.append("import ").append(gModel.getGrammarName()).append(";\n");
w.append('\n');
for(UPRule rule :rules) {
String ruleName = StringUtil.firstToLower(rule.getName());
generateAltGrammarCode(gModel, model, rules, rule, ruleName, w);
}
}
private static void generateAltGrammarCode(GrammarModel gModel, UnparserModel model, List rules,
UPRuleBase rule, String parentName, Writer w) throws IOException {
for(AlternativeBase a : rule.getAlternatives()) {
String aName = parentName + "Alt"+a.getId();
// filter alt name elements (have to be removed to produce a valid grammar)
String aText = a.getElements().stream().filter(e->!e.getText().
startsWith("#")).map(e->e.getText() + " ").collect(Collectors.joining());
w.append(aName+": ").append(aText + " EOF ;\n");
generateElementGrammarCode(gModel,model,rules,a,aName,w);
}
}
private static void generateElementGrammarCode(GrammarModel gModel, UnparserModel model, List rules,
AlternativeBase alt, String parentName, Writer w) throws IOException {
for(UPElement e : alt.getElements()) {
if(e instanceof SubRule) {
SubRule sr = (SubRule) e;
String srName = parentName+"SubRule"+sr.getId();
String eText = e.getText(); // TODO 27.12.2017 maybe simplify expression by removing all labels and substitute sub-rules?
w.append(srName+": ").append(eText+";\n");
generateAltGrammarCode(gModel, model, rules, sr, srName, w);
}
}
}
private static List computeFinalUnparserRuleList(UnparserModel model) {
// convert labeled alts to rule classes
Map> labeledAlternatives = model.getRules().stream().
flatMap(r->r.getAlternatives().stream()).filter(a->a instanceof LabeledAlternative).
map(a->(LabeledAlternative)a).collect(Collectors.groupingBy(LabeledAlternative::getName));
// add all directly specified rules
List rules = new ArrayList(model.getRules());
// find parents of labeled alternatives
List delList = labeledAlternatives.values().stream().
flatMap(las->las.stream()).map(la->la.getParentRule()).collect(Collectors.toList());
// and remove them from unparser generation since they are interface-only types and thus
// don't need/support direct implementations
rules.removeAll(delList);
// convert labeled alternatives to rules (which are added to rules list)
labeledAlternatives.values().stream().
map(la->labeledAltToRule(la.get(0).getName(),la)).collect(Collectors.toCollection(()->rules));
return rules;
}
private static List computeParentsOfLabeledAlts(UnparserModel model) {
// convert labeled alts to rule classes
Map> labeledAlternatives = model.getRules().stream().
flatMap(r->r.getAlternatives().stream()).filter(a->a instanceof LabeledAlternative).
map(a->(LabeledAlternative)a).collect(Collectors.groupingBy(LabeledAlternative::getName));
// find parents of labeled alternatives
List parentsOfLabeledAlts = labeledAlternatives.values().stream().
flatMap(las->las.stream()).map(la->(UPRule)la.getParentRule()).distinct().
collect(Collectors.toList());
return parentsOfLabeledAlts;
}
private static List computeRulesThatHaveSpecifiedParent(UnparserModel model, UPRule rParent) {
// find children of specified rule
List labeledAlternatives = rParent.getAlternatives().stream().
filter(a->a instanceof LabeledAlternative).
map(a->(LabeledAlternative)a).collect(Collectors.toList());
// convert labeled alts to rule classes
List children = labeledAlternatives.stream().
map(la->labeledAltToRule(la.getName(),
new ArrayList(Arrays.asList(la)))).
collect(Collectors.toList());
return children;
}
private static void generateUPParentUnparserCode(GrammarModel gModel, UnparserModel model,
List rules, Writer w) throws IOException {
w.append("package " + gModel.getPackageName()+".unparser;").append('\n').append('\n');
w.append("// Java API imports").append('\n');
w.append("import java.io.UnsupportedEncodingException;").append('\n');
w.append("import java.io.ByteArrayOutputStream;").append('\n');
w.append("import java.io.Writer;").append('\n');
w.append("import java.io.PrintWriter;").append('\n').append('\n');
w.append("import java.util.function.BiFunction;").append('\n');
w.append("// Model API imports").append('\n');
w.append("import "+gModel.getPackageName()+"." + gModel.getGrammarName() + "Model;").append('\n').append('\n');
w.append("import " + gModel.getPackageName() + ".CodeElement;").append('\n');
w.append("// rule imports (from model api)").append('\n');
w.append("import " + gModel.getPackageName() + ".*;").append('\n');
// We use star-import instead of importing each rule individually
// for (UPRule rImport : rules) {
//
// String ruleImportName = StringUtil.firstToUpper(rImport.getName());
//
// w.append("import " + gModel.getPackageName() + "." + ruleImportName + ";").append('\n');
// }
List parentsOfLabeledAlts = computeParentsOfLabeledAlts(model);
w.append('\n');
w.append("// alt parents imports (from model api)").append('\n');
for (UPRule rImport : parentsOfLabeledAlts) {
String ruleImportName = StringUtil.firstToUpper(rImport.getName());
w.append("import " + gModel.getPackageName() + "." + ruleImportName + ";").append('\n');
}
w.append('\n');
w.append("import " + gModel.getPackageName()+ ".unparser." +gModel.getGrammarName()+ "ModelUnparser.__VMF__IntValue;").append('\n');
w.append('\n');
w.append('\n');
w.append("public class "+gModel.getGrammarName()+"ModelUnparser {").append('\n');
w.append('\n');
w.append(" // rule unparsers").append('\n');
for (UPRule r : rules) {
String ruleName = StringUtil.firstToUpper(r.getName());
w.append(" private final " + ruleName + "Unparser " + StringUtil.firstToLower(ruleName) + "Unparser;").append('\n');
}
w.append('\n');
w.append(" private Formatter formatter = Formatter.newDefaultFormatter();").append('\n');
w.append(" public Formatter getFormatter() {return formatter;};").append('\n');
w.append(" public void setFormatter(Formatter formatter) { if(formatter==null) {formatter = Formatter.newDefaultFormatter();} this.formatter = formatter; };").append('\n');
w.append('\n');
w.append(" public "+gModel.getGrammarName()+"ModelUnparser() {").append('\n');
for (UPRule r : rules) {
String ruleName = StringUtil.firstToUpper(r.getName());
w.append(" " + StringUtil.firstToLower(ruleName) + "Unparser = new " + ruleName + "Unparser(this);").append('\n');
}
w.append(" }").append('\n');
String rootClassNameUpperCase = gModel.rootClass().nameWithUpper();
String rootClassNameLowerCase = gModel.rootClass().nameWithLower();
String rootClassUnparserName = rootClassNameLowerCase + "Unparser";
w.append('\n');
w.append(" public void unparse(" + gModel.getGrammarName()+"Model model, Writer w) {").append('\n');
w.append(" "+rootClassUnparserName+".unparse(model.getRoot(), new PrintWriter(w));").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append('\n');
w.append(" public void unparse(" + gModel.getGrammarName()+"Model model, PrintWriter w) {").append('\n');
w.append(" "+rootClassUnparserName+".unparse(model.getRoot(), w);").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append('\n');
w.append(" public String unparse(" + gModel.getGrammarName()+"Model model) {").append('\n');
w.append(" ByteArrayOutputStream output = new ByteArrayOutputStream();\n" +
" PrintWriter pw = new PrintWriter(output);\n" +
" unparse(model,pw);\n" +
" pw.close();\n" +
" return output.toString();").append('\n');
w.append(" }").append('\n');
w.append('\n');
for (UPRule r : parentsOfLabeledAlts) {
String ruleName = StringUtil.firstToUpper(r.getName());
String ruleUnparserInstanceName = StringUtil.firstToLower(r.getName())+"Unparser";
w.append('\n');
w.append(" public String unparse(" + ruleName+" rule) {").append('\n');
w.append(" ByteArrayOutputStream output = new ByteArrayOutputStream();\n" +
" PrintWriter pw = new PrintWriter(output);\n" +
" unparse(rule,pw);\n" +
" pw.close();\n" +
" return output.toString();\n"+
" }").append('\n');
w.append('\n');
w.append(" public void unparse(" + ruleName + " rule, Writer w) throws java.io.IOException {").append('\n');
List parents = computeRulesThatHaveSpecifiedParent(model, r);
boolean first = true;
for (UPRule r1 : parents) {
String ruleName1 = StringUtil.firstToUpper(r1.getName());
if(first) {
first=false;
w.append(" ");
} else {
w.append(" else ");
}
w.append("if ( rule instanceof " + ruleName1 + " ) {").append('\n');
w.append(" unparse( ("+ruleName1+")rule, w );").append('\n');
w.append(" }");
}
w.append('\n');
w.append(" w.flush();").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append('\n');
w.append(" public void unparse(" + ruleName + " rule, PrintWriter w) {").append('\n');
first = true;
for (UPRule r1 : parents) {
String ruleName1 = StringUtil.firstToUpper(r1.getName());
if(first) {
first=false;
w.append(" ");
} else {
w.append(" else ");
}
w.append("if ( rule instanceof " + ruleName1 + " ) {").append('\n');
w.append(" unparse( ("+ruleName1+")rule, w );").append('\n');
w.append(" }");
}
w.append('\n');
w.append(" w.flush();").append('\n');
w.append(" }").append('\n');
w.append('\n');
}
for (UPRule r : rules) {
String ruleName = StringUtil.firstToUpper(r.getName());
String ruleUnparserInstanceName = StringUtil.firstToLower(r.getName())+"Unparser";
w.append('\n');
w.append(" public void unparse(" + ruleName + " rule, Writer w) {").append('\n');
w.append(" "+ruleUnparserInstanceName+".unparse(rule, new PrintWriter(w));").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append('\n');
w.append(" public void unparse(" + ruleName + " rule, PrintWriter w) {").append('\n');
w.append(" "+ruleUnparserInstanceName+".unparse(rule, w);").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append(" public String unparse(" + ruleName+" rule) {").append('\n');
w.append(" ByteArrayOutputStream output = new ByteArrayOutputStream();\n" +
" PrintWriter pw = new PrintWriter(output);\n" +
" unparse(rule,pw);\n" +
" pw.close();\n" +
" return output.toString();\n"+
" }").append('\n');
w.append('\n');
}
w.append(" public void unparse(CodeElement rule, Writer w) throws java.io.IOException {").append('\n');
boolean first = true;
for (UPRule r : rules) {
String ruleName = StringUtil.firstToUpper(r.getName());
if(first) {
first=false;
w.append(" ");
} else {
w.append(" else ");
}
w.append("if ( rule instanceof " + ruleName + " ) {").append('\n');
w.append(" unparse( ("+ruleName+")rule, w );").append('\n');
w.append(" }");
}
w.append('\n');
w.append(" w.flush();").append('\n');
w.append(" }").append('\n');
w.append(" public void unparse(CodeElement rule, PrintWriter w) {").append('\n');
first = true;
for (UPRule r : rules) {
String ruleName = StringUtil.firstToUpper(r.getName());
if(first) {
first=false;
w.append(" ");
} else {
w.append(" else ");
}
w.append("if ( rule instanceof " + ruleName + " ) {").append('\n');
w.append(" unparse( ("+ruleName+")rule, w );").append('\n');
w.append(" }");
}
w.append('\n');
w.append(" w.flush();").append('\n');
w.append(" }").append('\n');
w.append('\n');
// Unparse methods for Strings
w.append('\n');
w.append(" /*package private*/ void unparse(String s, Writer w) throws java.io.IOException {").append('\n');
w.append(" w.append(s==null?\"\":s);").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append(" /*package private*/ void unparse(String s, PrintWriter w) {").append('\n');
w.append(" w.print(s==null?\"\":s);").append('\n');
w.append(" }").append('\n');
w.append('\n');
// State object classes
w.append('\n');
w.append(" static class __VMF__IntValue { private int value; public void set(int v) {this.value = v;} public int get() { return this.value; } int getAndInc() {int result = this.value;this.value++; return result;} }").append('\n');
w.append('\n');
w.append('\n');
w.append(" static class __VMF__BoolValue { private boolean value; public void set(boolean v) {this.value = v;} public boolean is() { return this.value; } boolean getAndInvert() {boolean result = this.value;this.value=!this.value; return result;} }").append('\n');
w.append('\n');
w.append("} // end class").append('\n');
w.append('\n');
}
private static void generateUPCode(GrammarModel gModel, UnparserModel model, List rules, ResourceSet resourceSet) {
for (UPRule r : rules) {
String ruleName = StringUtil.firstToUpper(r.getName());
try (Resource resource =
resourceSet.open(TypeUtil.computeFileNameFromJavaFQN(
gModel.getPackageName() + ".unparser." + ruleName + "Unparser"))) {
Writer w = resource.open();
w.append("package " + gModel.getPackageName()+".unparser;").append('\n').append('\n');
w.append("// Java API imports").append('\n');
w.append("import java.io.UnsupportedEncodingException;").append('\n');
w.append("import java.io.ByteArrayOutputStream;").append('\n');
w.append("import java.io.Writer;").append('\n');
w.append("import java.io.PrintWriter;").append('\n');
w.append("import java.util.Deque;").append('\n');
w.append("import java.util.ArrayDeque;").append('\n').append('\n');
w.append("// ANTLR4 imports").append('\n');
w.append("import org.antlr.v4.runtime.BailErrorStrategy;").append('\n');
w.append("import org.antlr.v4.runtime.CharStream;").append('\n');
w.append("import org.antlr.v4.runtime.CharStreams;").append('\n');
w.append("import org.antlr.v4.runtime.ParserRuleContext;").append('\n');
w.append("import org.antlr.v4.runtime.CommonTokenStream;").append('\n');
w.append("import org.antlr.v4.runtime.tree.ErrorNode;").append('\n');
w.append("import org.antlr.v4.runtime.tree.ParseTreeWalker;").append('\n').append('\n');
w.append("// ANTLR4 generated parser imports").append('\n');
w.append("import "+ gModel.getPackageName()+ ".unparser.antlr4."+gModel.getGrammarName()+"ModelUnparserGrammarLexer;").append('\n');
w.append("import "+ gModel.getPackageName()+ ".unparser.antlr4."+gModel.getGrammarName()+"ModelUnparserGrammarParser;").append('\n');
w.append("import "+ gModel.getPackageName()+ ".unparser.antlr4."+gModel.getGrammarName()+"ModelUnparserGrammarBaseListener;").append('\n').append('\n');
w.append("// Model API imports").append('\n');
w.append("import "+gModel.getPackageName()+"." + gModel.getGrammarName() + "Model;").append('\n').append('\n');
w.append('\n');
w.append("import " + gModel.getPackageName()+ ".unparser." +gModel.getGrammarName()+ "ModelUnparser.__VMF__IntValue;").append('\n');
w.append("import " + gModel.getPackageName()+ ".unparser." +gModel.getGrammarName()+ "ModelUnparser.__VMF__BoolValue;").append('\n');
w.append('\n');
w.append('\n');
w.append("// rule imports (from model api)").append('\n');
w.append("import " + gModel.getPackageName() + ".*;").append('\n');
// for (UPRule rImport : rules) {
//
// String ruleImportName = StringUtil.firstToUpper(rImport.getName());
//
// w.append("import " + gModel.getPackageName() + "." + ruleImportName + ";").append('\n');
// }
w.append('\n');
w.append("/*package private*/ public class " + ruleName + "Unparser {").append('\n');
// find rule
RuleClass gRule = gModel.getRuleClasses().stream().filter(
gRcl -> Objects.equals(StringUtil.firstToUpper(r.getName()), gRcl.nameWithUpper())).findFirst().get();
w.append('\n');
w.append(" // begin declare list property indices/iterators").append('\n');
for (Property prop : gRule.getProperties()) {
if (!prop.getType().isArrayType()) {
continue;
}
w.append(" __VMF__IntValue prop" + prop.nameWithUpper() + "ListIndex = new __VMF__IntValue();").append('\n');
w.append(" final Deque<__VMF__IntValue> prop"+ prop.nameWithUpper() + "State = new ArrayDeque<>();").append('\n');
}
w.append(" // end declare list property indices/iterators").append('\n');
w.append('\n');
w.append('\n');
w.append(" // begin declare property usage flags").append('\n');
for (Property prop : gRule.getProperties()) {
if (prop.getType().isArrayType()) {
continue;
}
w.append(" __VMF__BoolValue prop" + prop.nameWithUpper() + "Used = new __VMF__BoolValue();").append('\n');
w.append(" final Deque<__VMF__BoolValue> prop"+ prop.nameWithUpper() + "State = new ArrayDeque<>();").append('\n');
}
w.append(" // end declare property usage flags").append('\n');
w.append('\n');
w.append(" private final " + gModel.getGrammarName() + "ModelUnparser unparser;").append('\n');
w.append('\n');
w.append(" private void pushState() {").append('\n');
for (Property prop : gRule.getProperties()) {
if (!prop.getType().isArrayType()) {
continue;
}
w.append(" prop"+ prop.nameWithUpper() + "State.push( prop" + prop.nameWithUpper() + "ListIndex );").append('\n');
}
for (Property prop : gRule.getProperties()) {
if (prop.getType().isArrayType()) {
continue;
}
w.append(" prop"+ prop.nameWithUpper() + "State.push( prop" + prop.nameWithUpper() + "Used );").append('\n');
}
w.append(" }").append('\n');
w.append(" private void popState() {").append('\n');
for (Property prop : gRule.getProperties()) {
if (!prop.getType().isArrayType()) {
continue;
}
w.append(" prop" + prop.nameWithUpper() + "ListIndex = prop"+ prop.nameWithUpper() + "State.pop();").append('\n');
}
for (Property prop : gRule.getProperties()) {
if (prop.getType().isArrayType()) {
continue;
}
w.append(" prop" + prop.nameWithUpper() + "Used = prop"+ prop.nameWithUpper() + "State.pop();").append('\n');
}
w.append(" }").append('\n');
w.append('\n');
w.append(" /*package private*/ " + ruleName + "Unparser(" + gModel.getGrammarName() + "ModelUnparser unparser" + ") {").append('\n');
w.append(" this.unparser = unparser;").append('\n');
w.append(" }").append('\n');
w.append('\n');
w.append(" private " + gModel.getGrammarName() + "ModelUnparser getUnparser() { return this.unparser; }").append('\n').append('\n');
w.append(" public void " + "unparse(" + ruleName + " obj, PrintWriter w ) {").append('\n');
w.append('\n');
w.append(" // ignore null objects").append('\n');
w.append(" if(obj==null) return;").append('\n');
w.append('\n');
w.append(" pushState();").append('\n');
w.append('\n');
w.append(" // begin reset list property indices/iterators").append('\n');
for (Property prop : gRule.getProperties()) {
if (prop.getType().isArrayType()) {
w.append(" prop" + prop.nameWithUpper() + "ListIndex = new __VMF__IntValue();").append('\n');
}
}
w.append(" // end reset list property indices/iterators").append('\n');
w.append('\n');
w.append(" // begin reset property usage flags").append('\n');
for (Property prop : gRule.getProperties()) {
if (!prop.getType().isArrayType()) {
w.append(" prop" + prop.nameWithUpper() + "Used = new __VMF__BoolValue();").append('\n');
}
}
w.append(" // end reset property usage flags").append('\n');
w.append('\n');
w.append(" // try to unparse alternatives of this rule").append('\n');
// optimize alts (check the one with most elements first)
List alts = new ArrayList<>(r.getAlternatives());
//sortAltsMostElementsFirst(alts, rules);
for (AlternativeBase a : alts) {
String altName = ruleName + "Alt" + a.getId();
w.append(" if( unparse" + altName + "( obj, w ) ) { popState(); return; }").append('\n').append('\n');
}
w.append(" // TODO: 29.12.2017 introduce unparser error handler etc.").append('\n');
w.append(" throw new RuntimeException(\"Cannot unparse rule '" + ruleName + "'. Language model is invalid!\");").append('\n');
w.append(" // popState();").append('\n').append('\n');
w.append(" }").append('\n');
int altIndex = 0;
for (AlternativeBase a : r.getAlternatives()) {
altIndex++;
boolean noCheck = altIndex >= r.getAlternatives().size();
// TODO 06.01.2018 introduce switch for enabling/disabling full validation
// - remove false&& below to remove expensive checks from last alternative to speed up performance
// - do this only if you KNOW that your model is VALID
// generateAltCode(w, model, gModel, r, gRule, ruleName, ruleName, a,
// false&&noCheck);
//
// [UPDATE:] 22.06.2018 we can go even further and skip match... calls in almost all cases
// (only cases where properties are used in multiple alts need to be resolved via match...)
generateAltCode(w, model, gModel, r, gRule, ruleName, ruleName, a,
true, r.getAlternatives().size());
}
w.append("}").append('\n').append('\n');
} catch(IOException ex) {
ex.printStackTrace();
}
} // end for each rule
}
private static UPRule labeledAltToRule(String name, List la) {
UPRule r = UPRule.newBuilder().withName(name).build();
List alts = la.stream().map(UnparserCodeGenerator::labeledAltToAlt).collect(Collectors.toList());
r.getAlternatives().addAll(alts);
return r;
}
private static Alternative labeledAltToAlt(LabeledAlternative la) {
Alternative a = Alternative.newBuilder().applyFrom(la).build();
return a;
}
private static void generateSubRuleCode(String ruleName, String altName, String objName, SubRule sr, List rules, RuleClass gRule, Writer w) throws IOException {
w.append('\n');
w.append(" private boolean unparse" + altName + "SubRule" + sr.getId() + "( " + objName + " obj, PrintWriter w ) { boolean valid = false;").append('\n');
boolean multiplierCase = false;
if(sr instanceof UPElement) {
UPElement ruleElement = (UPElement) sr;
if(ruleElement.ebnfZeroMany() || ruleElement.ebnfOneMany()) {
multiplierCase = true;
}
}
if(multiplierCase) {
generateAltCodeForSubRulesWithMultiplier(altName, sr, rules, gRule, w);
} else {
// optimize alts (check the one with most elements first)
List alts = new ArrayList<>(sr.getAlternatives());
//sortAltsMostElementsFirst(alts, rules);
for(AlternativeBase a : sr.getAlternatives()) {
String altNameSub = altName + "SubRule" + sr.getId() + "Alt" + a.getId();
w.append(" if( unparse" + altNameSub + "( obj, w ) ) { return true; }").append('\n');
}
}
w.append(" return valid;}").append('\n');
}
private static int getTotalNumberOfElementsInAlt(AlternativeBase a, List rules) {
// TODO: 06.01.2018 does not fully work (some rule references, e.g., labeled rules cannot be resolved).
int eCount = 0;
for(UPElement e : a.getElements()) {
if(e instanceof SubRule) {
for(AlternativeBase subAlt : ((SubRule)e).getAlternatives()) {
eCount += getTotalNumberOfElementsInAlt(subAlt, rules);
}
} else if(e.isParserRule()) {
Optional ruleOpt = rules.stream().filter(
r->r.getName().equals(StringUtil.firstToUpper(e.getRuleName()))).findFirst();
if(ruleOpt.isPresent()) {
for(AlternativeBase ruleAlt : ruleOpt.get().getAlternatives()) {
eCount += getTotalNumberOfElementsInAlt(ruleAlt, rules);
}
} else {
eCount++;
}
} else {
eCount++;
}
}
return eCount;
}
/**
* Sorts the specified alternative list according to the total number of elements matched by the alternatives. This can
* improve unparsing performance. BUT: it does not work if the grammar contains ambiguities (will change the result!).
* Currently (as of 06.01.2018) we think it is better to leave this kind of optimization to the developer of the grammar.
* But we will keep this functionality for future benchmarks.
* @param alts
* @param rules
*/
private static void sortAltsMostElementsFirst(List alts, List rules) {
System.out.println("BEFORE:");
alts.forEach(alternativeBase -> {
System.out.println("a: " + "#elements: " + getTotalNumberOfElementsInAlt(alternativeBase, rules) + " " + alternativeBase.getText());
});
Collections.sort(alts, (a1, a2) -> Integer.compare(getTotalNumberOfElementsInAlt(a2, rules),getTotalNumberOfElementsInAlt(a1, rules)));
System.out.println("AFTER:");
alts.forEach(alternativeBase -> {
System.out.println("a: " + "#elements: " + getTotalNumberOfElementsInAlt(alternativeBase, rules) + " " + alternativeBase.getText());
});
}
private static void generateAltCodeForSubRulesWithMultiplier(String altName, SubRule sr, List rules, RuleClass gRule, Writer w) throws IOException {
w.append('\n');
w.append(" // begin declaring can-consume variables").append('\n');
for(AlternativeBase a : sr.getAlternatives()) {
String altNameSub = altName + "SubRule" + sr.getId() + "Alt" + a.getId();
String canConsumeVarName = StringUtil.firstToLower(altNameSub + "CanConsume");
w.append(" boolean " + canConsumeVarName + " = true;").append('\n');
}
w.append(" // end declaring can-consume variables").append('\n');
w.append('\n');
w.append(" // begin handling sub-rule with zeroToMany or oneToMany").append('\n');
w.append(" while(true) { ").append('\n');
w.append(" boolean matchedAnyAlt = false;").append('\n');
boolean first = true;
String elseStr = "";
// optimize alts (check the one with most elements first)
List alts = new ArrayList<>(sr.getAlternatives());
//sortAltsMostElementsFirst(alts, rules);
for(AlternativeBase a : alts) {
String altNameSub = altName + "SubRule" + sr.getId() + "Alt" + a.getId();
String canConsumeVarName = StringUtil.firstToLower(altNameSub + "CanConsume");
if(!first) {
elseStr = " else";
} else {
first=false;
}
w.append(" " + elseStr + " if( "
+ canConsumeVarName + " && unparse" + altNameSub + "( obj, w ) ) { valid = true;").append('\n').append('\n');
w.append(" // We matched this alternative").append('\n');
w.append(" matchedAnyAlt = true;").append('\n').append('\n');
w.append(" // We unparsed this alt once. For unparsing multiple times there has to be something").append('\n');
w.append(" // to be consumed/unparsed. See below...").append('\n');
w.append(" " + canConsumeVarName + " = false;").append('\n').append('\n');
List propertiesInAlt = getPropertiesUsedInAlt(gRule,a);
if(propertiesInAlt.isEmpty()) {
w.append(" // We don't unparse this alt again since there is nothing to consume (no properties).").append('\n').append('\n');
} else {
w.append(" // We check whether at least one of the rule properties/elements of list properties can").append('\n');
w.append(" // be consumed/unparsed. If one of the checks below is positive we unparse again.").append('\n').append('\n');
}
// array properties
for(Property p : propertiesInAlt) {
if(p.getType().isArrayType()) {
w.append(" // check whether elements from list property '"+ p.nameWithLower() + "' can be consumed").append('\n');
w.append(" " +canConsumeVarName + " = " + canConsumeVarName + "\n" +
" || prop" + p.nameWithUpper()+ "ListIndex.get() < obj.get" + p.nameWithUpper() +"().size();").append('\n');
}
}
// non-array properties
for(Property p : propertiesInAlt) {
if(!p.getType().isArrayType()) {
w.append(" // check whether non-list property '"+ p.nameWithLower() + "' can be consumed").append('\n');
w.append(" " +canConsumeVarName + " = " + canConsumeVarName + "\n" +
" || ( obj.get" + p.nameWithUpper() + "()!=null && !prop" + p.nameWithUpper()+ "Used.is());").append('\n');
}
}
w.append(" }").append('\n');
}
w.append(" if( matchedAnyAlt == false ) break;").append('\n');
w.append(" }").append('\n');
w.append(" // end handling sub-rule with zeroToMany or oneToMany").append('\n').append('\n');
}
private static void generateAltCode(Writer w, UnparserModel model, GrammarModel gModel, UPRule r, RuleClass gRule,
String ruleName, String objName, AlternativeBase a, boolean noCheck, int numAltsPerRule) throws IOException {
boolean userNoCheck = noCheck;
boolean lastRuleAlt = a.getId() == numAltsPerRule-1;
boolean negationOperatorUsedInAlt = propertiesOfAltUseNegateOperator(a, r, gRule);
boolean propertiesUsedInMultipleAlts = propertiesUsedInMultipleRuleAlts(a, r, gRule);
// we do need to check cases where properties are used in multiple alts of the current rule to ensure we
// do a valid unparse
if(noCheck == true) {
if(lastRuleAlt) {
// if we are the last rule we never need a check
} else {
// if not, it depends...
noCheck = !propertiesUsedInMultipleAlts && !negationOperatorUsedInAlt;
}
}
w.append('\n');
w.append(" // ").append('\n');
w.append(" // --------------------------------------------------------------------------------").append('\n');
w.append(" // -- FLAGS:").append('\n');
w.append(" // --------------------------------------------------------------------------------").append('\n');
w.append(" // ").append('\n');
w.append(" // ------------------------------------------------------------------------------").append('\n');
w.append(" // -- rule and noCheck info:").append('\n');
w.append(" // ------------------------------------------------------------------------------").append('\n');
w.append(" // -> rule-alt-text: ").append(a.getText().replace('\n',' ').
replace('\r',' ')).append('\n');
w.append(" // -> no-check: " + userNoCheck).append('\n');
w.append(" // ------------------------------------------------------------------------------").append('\n');
w.append(" // -- properties which determine whether we can unparse without matchAlt-calls:").append('\n');
w.append(" // ------------------------------------------------------------------------------").append('\n');
w.append(" // -> no-operator: " + negationOperatorUsedInAlt).append('\n');
w.append(" // -> used-in-multiple-alts: " + propertiesUsedInMultipleAlts).append('\n');
w.append(" // -> last-rule-alt: " + lastRuleAlt).append('\n');
w.append(" //").append('\n');
w.append(" // --------------------------------------------------------------------------------").append('\n');
w.append(" // -- EVALUATION:").append('\n');
w.append(" // --------------------------------------------------------------------------------").append('\n');
w.append(" // ").append('\n');
if(userNoCheck==false) {
w.append(" // noCheck is disabled which forces us to do all checks.").append('\n');
w.append(" // FIXME: TODO: consider disabling checks and do full validation prior to unparsing.").append('\n');
w.append(" // FIXME: TODO: this code will run up to ~70 times slower than with noCheck=true").append('\n');
} else if(lastRuleAlt) {
w.append(" // We are the last alt in this rule and don't do any checks (matchAlt-calls)").append('\n');
w.append(" // since checking was not enforced.").append('\n');
} else if(negationOperatorUsedInAlt) {
w.append(" // Negation operator '~' is used in this alt.").append('\n');
w.append(" // That's why we do checks (matchAlt-calls). Otherwise we can't make a valid decision.").append('\n');
w.append(" // FIXME: TODO: using the not-operator in parser-rule properties has a negative performance impact.").append('\n');
} else if(propertiesUsedInMultipleAlts) {
w.append(" // Properties used in this alt are used in other alts (with different terminals/lexer rules).").append('\n');
w.append(" // That's why we do checks (matchAlt-calls). Otherwise we can't make a valid decision.").append('\n');
w.append(" // FIXME: TODO: using properties with different lexer rules in multiple alts has a negative performance impact.").append('\n');
} else {
w.append(" // Nothing prevents us from skipping checks (matchAlt-calls). We can decide by checking").append('\n');
w.append(" // whether properties in this alt are consumable and/or if properties used in other alts").append('\n');
w.append(" // are defined etc.").append('\n');
}
w.append(" // ");
String altName = ruleName + "Alt" + a.getId();
w.append('\n');
w.append(" private boolean unparse"+ altName + "( " + objName + " obj, PrintWriter w ) {").append('\n');
w.append('\n');
w.append(" if(obj==null) return false;").append('\n');
w.append('\n');
w.append(" // begin check whether unused properties are set").append('\n');
// alternatives of grammar rules need to check whether they use all properties that are set and do
// not use any unset property (only they do, no sub-rules, hence the if statement)
if(a.getParentRule() == r) {
generateUnusedPropertiesCheck(a, r, w);
}
w.append(" // end check whether unused properties are set").append('\n');
w.append('\n');
w.append(" // begin check whether non-optional properties are available (not used/consumed)").append('\n');
// TODO 21.01.2018 enable if we want to avoid unnecessary matchAlt... calls but still do validation
generateConsumedPropertiesCheck(a, altName, r, gRule, w);
w.append(" // end check whether non-optional properties are available (not used/consumed)").append('\n');
w.append('\n');
if(!noCheck) {
w.append(" getUnparser().getFormatter().pushState();").append('\n');
}
if(noCheck) {
w.append(" PrintWriter internalW = w;").append('\n');
} else {
w.append(" ByteArrayOutputStream output = new ByteArrayOutputStream();").append('\n');
w.append(" PrintWriter internalW = new PrintWriter(output);").append('\n');
}
w.append('\n');
w.append(" // begin preparing local list indices/iterators").append('\n');
for(Property prop : gRule.getProperties()) {
if(!prop.getType().isArrayType()) {
continue;
}
w.append(" int prevProp" + prop.nameWithUpper() + "ListIndex = prop"
+ prop.nameWithUpper() + "ListIndex.get();").append('\n');
}
w.append(" // end preparing local list indices/iterators").append('\n');
w.append('\n');
w.append(" // begin preparing local property usage flags").append('\n');
for(Property prop : gRule.getProperties()) {
if(prop.getType().isArrayType()) {
continue;
}
w.append(" boolean prevProp" + prop.nameWithUpper() + "Used = prop"
+ prop.nameWithUpper() + "Used.is();").append('\n');
}
w.append(" // end preparing local property usage flags").append('\n');
generateElements(model, gModel, w, gRule, r, a, altName, noCheck);
if(noCheck) {
w.append(" return true;");
w.append("\n }").append('\n').append('\n');
} else {
w.append('\n');
w.append(" internalW.close();");
w.append('\n');
w.append("\n String s;").append('\n');
w.append(" try {").append('\n');
w.append(" s = output.toString(\"UTF-8\");").append('\n');
w.append(" } catch(UnsupportedEncodingException ex) {").append('\n');
w.append(" s = output.toString();").append("\n");
w.append(" ex.printStackTrace();").append('\n');
w.append(" }").append('\n');
w.append("\n if( match" + altName + "(s) ) {").append('\n');
w.append(" w.print(s /*+ \" \"*/);").append('\n');
w.append('\n');
w.append(" getUnparser().getFormatter().acceptState();").append('\n');
w.append('\n');
w.append(" return true;").append('\n');
w.append(" } else {").append('\n');
w.append('\n');
generateRejectStateCode(" ",gRule, altName, noCheck, w);
w.append('\n');
w.append(" return false;").append('\n');
w.append(" }").append('\n');
w.append("\n }").append('\n').append('\n');
} // end if !noCheck
generateMatchAltMethod(gModel, altName, w);
final boolean nocheckFinal = noCheck;
a.getElements().stream().filter(el->el instanceof SubRule).filter(el->!(el instanceof UPNamedSubRuleElement)).
map(el->(SubRule)el).forEach(sr-> {
for(AlternativeBase sa : sr.getAlternatives()) {
try {
generateAltCode(w, model, gModel, r, gRule, altName + "SubRule" + sr.getId(), objName, sa, /*we check based on parent alts preferences*/nocheckFinal,sr.getAlternatives().size());
} catch (IOException e) {
e.printStackTrace();
}
}
try {
generateSubRuleCode(ruleName, altName, objName, sr, model.getRules(), gRule, w);
} catch (IOException e) {
e.printStackTrace();
}
});
}
private static void generateRejectStateCode(String indent, RuleClass gRule, String altName, boolean noCheck, Writer w) throws IOException {
w.append(indent + "// begin revert global list indices/iterators since we didn't consume this alt").append('\n');
for (Property prop : gRule.getProperties()) {
if (!prop.getType().isArrayType()) {
continue;
}
w.append(indent + "prop" + prop.nameWithUpper() + "ListIndex.set(prevProp" + prop.nameWithUpper() + "ListIndex);").append('\n');
}
w.append(indent + "// end revert global list indices/iterators since we didn't consume this alt").append('\n');
w.append('\n');
w.append('\n');
w.append(indent + "// begin revert global property usage flags since we didn't consume this alt").append('\n');
for (Property prop : gRule.getProperties()) {
if (prop.getType().isArrayType()) {
continue;
}
w.append(indent + "prop" + prop.nameWithUpper() + "Used.set(prevProp" + prop.nameWithUpper() + "Used);").append('\n');
}
w.append(indent + "// end update global property usage flags since we consume this alt").append('\n');
w.append('\n');
if(!noCheck) {
w.append(indent + "getUnparser().getFormatter().rejectState();").append('\n');
}
}
private static void _getNamesOfPropertiesUsedInAlternative(AlternativeBase a, List propertyNames, Map listTypeMap) {
for(UPElement e : a.getElements()) {
if(e instanceof WithName) {
String pName = ((WithName)e).getName();
propertyNames.add(pName);
if(listTypeMap!=null) {
listTypeMap.put(pName, e.isListType());
}
}
if(e instanceof SubRule) {
SubRule sr = (SubRule) e;
for(AlternativeBase subA : sr.getAlternatives()) {
_getNamesOfPropertiesUsedInAlternative(subA,propertyNames, listTypeMap);
}
}
}
}
private static List getNamesOfPropertiesUsedInAlternative(AlternativeBase a) {
List propertyNames = new ArrayList<>();
_getNamesOfPropertiesUsedInAlternative(a, propertyNames, null);
return propertyNames;
}
private static void _getPropertiesElementsUsedInAlternative(AlternativeBase a, List elements, boolean includeSubRules) {
for(UPElement e : a.getElements()) {
if(e instanceof WithName) {
elements.add(e);
}
if(includeSubRules && e instanceof SubRule) {
SubRule sr = (SubRule) e;
for(AlternativeBase subA : sr.getAlternatives()) {
_getPropertiesElementsUsedInAlternative(subA,elements, includeSubRules);
}
}
}
}
private static List getPropertiesElementsUsedInAlternative(AlternativeBase a, boolean includeSubRules) {
List elements = new ArrayList<>();
_getPropertiesElementsUsedInAlternative(a, elements, includeSubRules);
return elements;
}
private static List getPropertiesUsedInSubRule(RuleClass parent, SubRule sr) {
List propNames = new ArrayList<>();
for(AlternativeBase subA : sr.getAlternatives()) {
propNames.addAll(getNamesOfPropertiesUsedInAlternative(subA));
}
return parent.getProperties().stream().filter(
p->propNames.contains(p.nameWithLower())).collect(Collectors.toList());
}
private static List getPropertiesUsedInAlt(RuleClass parent, AlternativeBase a) {
List propNames = new ArrayList<>();
propNames.addAll(getNamesOfPropertiesUsedInAlternative(a));
return parent.getProperties().stream().filter(
p->propNames.contains(p.nameWithLower())).collect(Collectors.toList());
}
private static void generateUnusedPropertiesCheck(AlternativeBase a, UPRule r, Writer w) throws IOException {
List propertyNamesUsed = getNamesOfPropertiesUsedInAlternative(a);
Map propertyNamesInRuleWithListFlag = getPropertyNamesOfRule(r);
List propertiesNotUsedInAlt = new ArrayList<>();
propertiesNotUsedInAlt.addAll(propertyNamesInRuleWithListFlag.keySet());
propertiesNotUsedInAlt.removeAll(propertyNamesUsed);
for(String pName : propertiesNotUsedInAlt) {
// TODO 19.06.2018 check for set/unset
// if(propertyNamesInRuleWithListFlag.get(pName)) {
// w.append(" if( !obj.get" + StringUtil.firstToUpper(pName) + "().isEmpty() ) return false;").append('\n');
// } else {
// w.append(" if( obj.get" + StringUtil.firstToUpper(pName) + "() !=null ) return false;").append('\n');
// }
w.append(" if( obj.vmf().reflect().propertyByName(\"" + StringUtil.firstToLower(pName) + "\").get().isSet() ) return false;").append('\n');
}
}
private static void generateConsumedPropertiesCheck(AlternativeBase a, String aName, UPRule r, RuleClass gRule, Writer w) throws IOException {
//List propertiesInAlt = getPropertiesUsedInAlt(gRule, a);
List propertyElements = getPropertiesElementsUsedInAlternative(a, false);
String canConsumeVarName = StringUtil.firstToLower(aName + "CanConsume");
boolean hasArrayPropertiesThatNeedToBeChecked = propertyElements.stream().
filter(p->p.isListType() && !(p.ebnfOptional() || p.ebnfZeroMany())).count() > 0;
boolean hasNonArrayPropertiesThatNeedToBeChecked = propertyElements.stream().
filter(p->!p.isListType() && !p.ebnfOptional()).count() > 0;
boolean hasToCheck = hasArrayPropertiesThatNeedToBeChecked || hasNonArrayPropertiesThatNeedToBeChecked;
if(hasToCheck) {
w.append(" boolean " + canConsumeVarName + " = true;").append('\n');
} else {
w.append(" // no non-optional properties to check").append('\n');
}
// array properties
for(UPElement p : propertyElements) {
if(p.isListType() && !(p.ebnfOptional() || p.ebnfZeroMany())) {
String pName = StringUtil.firstToLower(((WithName)p).getName());
String pNameUpper = StringUtil.firstToUpper(((WithName)p).getName());
w.append(" // check whether elements from list property '"+ pName + "' can be consumed").append('\n');
w.append(" " +canConsumeVarName + " = " + canConsumeVarName + "\n" +
" && prop" + pNameUpper+ "ListIndex.get() < obj.get" + pNameUpper +"().size();").append('\n');
}
}
// non-array properties
for(UPElement p : propertyElements) {
if(!p.isListType() && !(p.ebnfOptional() || p.ebnfZeroMany())) {
String pName = StringUtil.firstToLower(((WithName)p).getName());
String pNameUpper = StringUtil.firstToUpper(((WithName)p).getName());
w.append(" // check whether non-list property '"+ pName + "' can be consumed").append('\n');
w.append(" " +canConsumeVarName + " = " + canConsumeVarName + "\n" +
" && ( obj.get" + pNameUpper + "()!=null /*&& !prop" + pNameUpper+ "Used.is()*/);").append('\n');
}
}
if(hasToCheck) {
w.append(" if(!" + canConsumeVarName + ") return false;").append('\n');
}
}
private static boolean propertiesOfAltUseNegateOperator(AlternativeBase a, UPRule r, RuleClass gRule) {
// negation can only occur in lexer rules and terminal rules
Stream propertyElemsUsedInAlt = getPropertiesElementsUsedInAlternative(a, true).
stream().distinct();
// check whether we find a rule that is negated
return propertyElemsUsedInAlt.filter(e->e.isNegated()).count() > 0;
}
private static boolean propertiesUsedInMultipleRuleAlts(AlternativeBase a, UPRule r, RuleClass gRule) {
// find overlapping properties, i.e., properties that are used in multiple alternatives of the same rule
List rulePropertyElements = new ArrayList<>();
for(AlternativeBase ruleAlt: r.getAlternatives()) {
List propertyElemsUsedInAlt = getPropertiesElementsUsedInAlternative(ruleAlt, true).
stream().distinct().filter(e->e.isLexerRule() || e.isTerminal()).collect(Collectors.toList());
// for each element we check whether to add the element to the list of property elements to count
for(UPElement upElement : propertyElemsUsedInAlt) {
String name = "";
String referencedRuleName;
if(upElement.isTerminal()) {
referencedRuleName = upElement.getText();
} else {
referencedRuleName = upElement.getRuleName();
}
// add the element (if not already added)
if(!rulePropertyElements.contains(upElement)) {
rulePropertyElements.add(upElement);
}
// filter all elements from the list that have the same name, reference a lexer rule or terminal
// which is different from the current element (upElement).
// we don't count parser-rule-elements. They have their own unparser class and do their checks there
// and the same property cannot be of two different parser rule types
List elementsToAdd = rulePropertyElements.stream().filter(e -> e instanceof WithName).
filter(n -> Objects.equals(((WithName)n).getName(), name)).
filter(n->!Objects.equals(n.isTerminal()?n.getText():n.getRuleName(),referencedRuleName)).
collect(Collectors.toList());
// finally add the elements
rulePropertyElements.addAll(elementsToAdd);
}
}
// this map contains the properties and the number of occurrences
Map numberOfOccurrences = new HashMap<>();
// do the counting
for(Property p : gRule.getProperties()) {
String pName = p.nameWithLower();
int numOcc = (int)rulePropertyElements.stream().filter(e->e instanceof WithName).map(e->(WithName)e).
filter(n-> Objects.equals(pName,n.getName())).count();
numberOfOccurrences.put(pName, numOcc);
}
// check whether for at least one property we have multiple occurrences (as explained above, we only check
// lexer rule properties for now)
boolean overlappingProperties = numberOfOccurrences.values().stream().filter(v->v > 1).count() > 0;
return overlappingProperties;
}
private static Map getPropertyNamesOfRule(UPRule r) {
List propertyNames = new ArrayList<>();
Map listType = new HashMap<>();
for(AlternativeBase a : r.getAlternatives()) {
_getNamesOfPropertiesUsedInAlternative(a, propertyNames, listType);
}
return listType;
}
private static TemplateEngine tEngine;
private static void generateMatchAltMethod(GrammarModel gModel, String altName, Writer w) throws IOException {
if(tEngine==null) {
tEngine = new TemplateEngine();
}
TemplateEngine.Engine engine = tEngine.getEngine();
VelocityContext context = engine.context;
context.put("model", gModel);
context.put("altName", altName);
context.put("grammarName", gModel.getGrammarName());
context.put("packageName", gModel.getPackageName());
context.put("Util", StringUtil.class);
tEngine.mergeTemplate("model-unparser-match-alt",w);
}
private static void generateElements(UnparserModel model, GrammarModel gModel, Writer w, RuleClass gRule,
UPRule rule, AlternativeBase a, String altName, boolean noCheck) throws IOException {
String indent = "";
for(UPElement e : a.getElements()) {
w.append('\n');
if(e instanceof UPSubRuleElement) {
generateSubRuleElementCode(w, altName, indent, (UPSubRuleElement) e, gRule, noCheck);
} else if(e instanceof UPNamedSubRuleElement) {
generateNamedSubRuleElementCode(w, indent, (UPNamedSubRuleElement) e, gRule, altName, noCheck);
} else if(e instanceof UPNamedElement) {
UPNamedElement sre = (UPNamedElement) e;
generateNamedElementCode(w, indent, model, sre, rule, gRule, gModel, altName, noCheck);
} else {
generateUnnamedElementCode(model, w, indent, e);
}
}
}
private static void generateUnnamedElementCode(UnparserModel model, Writer w, String indent, UPElement e) throws IOException {
// ignore EOF element
if("EOF".equals(e.getText().trim())) {
return;
}
// remove ebnf multiplicity, optional and greedy characters
String eText = e.getText();
eText = removeEBNFModifierFromElementText(eText);
if(eText.startsWith("'")) {
// terminal element
// remove '
eText = eText.substring(1,eText.length()-1);
w.append(indent + " // handling unnamed terminal element '"+eText+"'").append('\n');
String ruleString = StringUtil.escapeJavaStyleString(eText,true);
String ruleInfoString = "Formatter.RuleInfo.newRuleInfo(obj, Formatter.RuleType.TERMINAL, null, \"" + ruleString + "\")";
w.append(indent + " getUnparser().getFormatter().pre( unparser, " + ruleInfoString + ", internalW);").append('\n');
w.append(indent + " internalW.print( \""+StringUtil.escapeJavaStyleString(eText,true) + "\");").append('\n');
w.append(indent + " getUnparser().getFormatter().post(unparser, " + ruleInfoString + ", internalW);").append('\n');
return;
} else if(Character.isUpperCase(eText.charAt(0))){
// we are a lexer rule ref
final String lexerRuleName = eText;
Optional lexerRuleOptional =
model.getLexerRules().stream().
filter(lr->Objects.equals(lr.getName(),lexerRuleName)).findFirst();
boolean lexerRuleToTerminalPossible = lexerRuleOptional.isPresent();
if(lexerRuleToTerminalPossible) {
UPLexerRule lexerRule = lexerRuleOptional.get();
String lexerRuleString = removeEBNFModifierFromElementText(lexerRule.getText());
if(lexerRuleString.startsWith("'")) {
// terminal element
// remove '
// TODO 18.01.2018 improve terminal extraction from lexer rule
lexerRuleString = lexerRuleString.substring(1, lexerRuleString.length() - 1);
lexerRuleString = lexerRuleString.replaceAll("'\\s*'","");
w.append(indent + " // handling unnamed lexer rule ref '" + eText + "'").append('\n');
w.append(indent + " // we could successfully find terminal text of the rule").append('\n');
String ruleName = eText;
String ruleInfoString = "Formatter.RuleInfo.newRuleInfo(obj, Formatter.RuleType.LEXER_RULE, \"" + ruleName + "\", \"" + StringUtil.escapeJavaStyleString(lexerRuleString, true) + "\")";
w.append(indent + " getUnparser().getFormatter().pre( unparser, " + ruleInfoString + ", internalW);").append('\n');
w.append(indent + " internalW.print( \"" + StringUtil.escapeJavaStyleString(lexerRuleString, true) + "\" /*+ \" \" */);").append('\n');
w.append(indent + " getUnparser().getFormatter().post(unparser, " + ruleInfoString + ", internalW);").append('\n');
return;
} else {
w.append(indent+" // handling unnamed lexer rule ref '"+eText+"'").append('\n');
w.append(indent+" // FIXME: cannot process rule since it is not terminal only (that's why we ignore it)").append('\n');
w.append(indent+" // RULE-TEXT: " + lexerRuleString).append('\n');
w.append(indent+" // TODO SOLUTION: specify a property name, e.g., 'myProperty = " +eText+ "'").append('\n');
w.append(indent+" // getUnparser().getFormatter().pre( unparser, obj, \""+StringUtil.escapeJavaStyleString(eText,true)+"\", internalW);").append('\n');
w.append(indent+" // internalW.print( \""+StringUtil.escapeJavaStyleString(eText,true) + "\" );").append('\n');
w.append(indent+" // getUnparser().getFormatter().post(unparser, obj, \""+StringUtil.escapeJavaStyleString(eText,true)+"\", internalW);").append('\n');
return;
}
}
}
w.append(indent+" // handling unrecognized element '"+eText.replace('\n', ' ')+"'").append('\n');
w.append(indent+" // FIXME: cannot recognize element (that's why we ignore it)").append('\n');
w.append(indent+" // getUnparser().getFormatter().pre( unparser, obj, \""+StringUtil.escapeJavaStyleString(eText,true)+"\", internalW);").append('\n');
w.append(indent+" // internalW.print( \""+StringUtil.escapeJavaStyleString(eText,true) + "\" );").append('\n');
w.append(indent+" // getUnparser().getFormatter().post(unparser, obj, \""+StringUtil.escapeJavaStyleString(eText,true)+"\", internalW);").append('\n');
}
private static String removeEBNFModifierFromElementText(String eText) {
if(eText.endsWith("?")) {
eText = eText.substring(0,eText.length()-1);
}
if(eText.endsWith("*")) {
eText = eText.substring(0,eText.length()-1);
}
if(eText.endsWith("+")) {
eText = eText.substring(0,eText.length()-1);
}
if(eText.endsWith(")")) {
// remove ( )
eText = eText.substring(1,eText.length()-1);
}
return eText;
}
private static void generateNamedElementCode(Writer w, String indent, UnparserModel model, UPNamedElement sre,
UPRule rule, RuleClass gRule, GrammarModel gModel, String altName,
boolean noCheck) throws IOException {
String lexerRuleName = sre.getRuleName()!=null?sre.getRuleName():"";
w.append(indent+" // handling element with name '"+sre.getName()+"'").append('\n');
String ruleType = "/*FIXME: TYPE IS UNDEFINED! ruleText='" + sre.getText() + "' */";
if(sre.isLexerRule()) {
ruleType = "Formatter.RuleType.LEXER_RULE";
} else if(sre.isTerminal()) {
ruleType = "Formatter.RuleType.TERMINAL";
}
String ruleInfoString;
if(sre.isListType()) {
String indexName = "prop" + StringUtil.firstToUpper(sre.getName()) + "ListIndex";
String propName = "obj.get" + StringUtil.firstToUpper(sre.getName()+"()");
if(sre.ebnfOne()) {
if(sre.ebnfOptional()) {
w.append(indent+" if(" + indexName+ ".get()" +" < " +propName+ ".size() ) {").append('\n');
if(sre.isParserRule()) {
w.append(indent+" " + sre.getRuleName() + " listElemObj = obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc());").append('\n');
w.append(indent+" getUnparser().unparse(listElemObj, internalW );").append('\n');
} else if(sre.isLexerRule()) {
w.append(indent+" {").append('\n');
String targetTypeOfMapping = gModel.getTypeMappings().targetTypeNameOfMapping(rule.getName(), lexerRuleName);
boolean mappingExists = gModel.getTypeMappings().mappingByRuleNameExists(rule.getName(), lexerRuleName);
w.append(indent+" " + targetTypeOfMapping + " listElemObj = obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc());").append('\n');
w.append(indent+" String s = TypeToStringConverterForRule"+ StringUtil.firstToUpper(rule.getName()) + ".convertToString" + (mappingExists?"ForRule"+lexerRuleName:"") + "( listElemObj )").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
} else {
w.append(indent+" String listElemObj = obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName + ".getAndInc());").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", listElemObj /*TERMINAL String conversion*/" + ")");
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post( unparser, ruleInfo, internalW);").append('\n');
}
w.append(indent + " }").append('\n');
} else {
String breakOrReturn = " /*non optional case*/ return false;";
w.append(indent+" if(" + indexName + ".get()" +" > " +propName+ ".size() -1 || " + propName + ".isEmpty()) {").append('\n');
generateRejectStateCode(indent+" ",gRule, altName, noCheck, w);
w.append(indent+" " +breakOrReturn).append('\n');
w.append(indent+" }").append('\n');
if(sre.isParserRule()) {
w.append(indent+" getUnparser().unparse( obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()), internalW );").append('\n');
} else if(sre.isLexerRule()) {
w.append(indent+" {").append('\n');
boolean mappingExists = gModel.getTypeMappings().mappingByRuleNameExists(rule.getName(), lexerRuleName);
w.append(indent+" String s = TypeToStringConverterForRule"+ StringUtil.firstToUpper(rule.getName()) + ".convertToString"+(mappingExists?"ForRule"+lexerRuleName:"")+"( obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()) );").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
} else {
w.append(indent+" {").append('\n');
w.append(indent+" String s = obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()) /*TERMINAL String conversion*/;").append('\n');
w.append(indent+" if(s !=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", listElemObj.toString() /*TERMINAL String conversion*/" + ")");
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
}
}
} else if (sre.ebnfOneMany() || sre.ebnfZeroMany()) {
if(sre.ebnfOptional()||sre.ebnfZeroMany()) {
w.append(indent+" while(" + indexName+ ".get()" +" < " +propName+ ".size() ) {").append('\n');
if(sre.isParserRule()) {
w.append(indent+" getUnparser().unparse( obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()), internalW );").append('\n');
} else if(sre.isLexerRule()) {
w.append(indent+" {").append('\n');
boolean mappingExists = gModel.getTypeMappings().mappingByRuleNameExists(rule.getName(), lexerRuleName);
w.append(indent+" String s = TypeToStringConverterForRule"+ StringUtil.firstToUpper(rule.getName()) + ".convertToString"+(mappingExists?"ForRule"+lexerRuleName:"")+"( obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()) );").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
} else {
w.append(indent+" {").append('\n');
w.append(indent+" String s = obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()) /*TERMINAL String conversion*/;").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
}
w.append(indent + " }").append('\n');
} else {
w.append(indent+" boolean matched"+StringUtil.firstToUpper(sre.getName()) +" = false;").append('\n');
w.append(indent+" while(" + indexName+ ".get()" +" < " +propName+ ".size() && !" + propName + ".isEmpty()) {").append('\n');
if(sre.isParserRule()) {
w.append(indent + " matched"+StringUtil.firstToUpper(sre.getName()) +" = true;").append('\n');
w.append(indent + " getUnparser().unparse( obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()), internalW );").append('\n');
} else if(sre.isLexerRule()) {
w.append(indent+" {").append('\n');
boolean mappingExists = gModel.getTypeMappings().mappingByRuleNameExists(rule.getName(), lexerRuleName);
w.append(indent+" String s = TypeToStringConverterForRule"+ StringUtil.firstToUpper(rule.getName()) + ".convertToString"+(mappingExists?"ForRule"+lexerRuleName:"")+"( obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()) );").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
} else {
w.append(indent+" {").append('\n');
w.append(indent+" String s = obj.get" + StringUtil.firstToUpper(sre.getName()) + "().get(" + indexName +".getAndInc()).toString() /*TERMINAL String conversion*/;").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
}
w.append(indent+" }").append('\n');
w.append(indent+" // we are in the non-optional case and return early if we didn't match").append('\n');
w.append(indent+" if(!matched"+StringUtil.firstToUpper(sre.getName())+") { ").append('\n');
generateRejectStateCode(" ",gRule, altName, noCheck, w);
w.append(indent+" return false;").append('\n');
w.append(indent+" }").append('\n');
}
}
} else {
if(sre.isParserRule()) {
w.append(indent+" if(obj.get" + StringUtil.firstToUpper(sre.getName()) + "() !=null) {").append('\n');
w.append(indent+" getUnparser().unparse( obj.get" + StringUtil.firstToUpper(sre.getName()) + "(), internalW );").append('\n');
w.append(indent+" }").append('\n');
} else if(sre.isLexerRule()) {
w.append(indent + " if(!prop" + StringUtil.firstToUpper(sre.getName()) + "Used.is())").append('\n');
w.append(indent + " {").append('\n');
w.append(indent + " prop" + StringUtil.firstToUpper(sre.getName()) + "Used.set(true);").append('\n');
boolean mappingExists = gModel.getTypeMappings().mappingByRuleNameExists(rule.getName(), lexerRuleName);
w.append(indent + " String s = TypeToStringConverterForRule"+ StringUtil.firstToUpper(rule.getName()) + ".convertToString"+(mappingExists?"ForRule"+lexerRuleName:"")+"( obj.get" + StringUtil.firstToUpper(sre.getName()) + "() );").append('\n');
w.append(indent + " if(s!=null) {").append('\n');
w.append(indent + " Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent + " getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent + " internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent + " getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent + " }").append('\n');
w.append(indent + " }").append('\n');
} else {
w.append(indent + " if(!prop" + StringUtil.firstToUpper(sre.getName()) + "Used.is())").append('\n');
w.append(indent + " {").append('\n');
w.append(indent + " prop" + StringUtil.firstToUpper(sre.getName()) + "Used.set(true);").append('\n');
w.append(indent + " String s = obj.get" + StringUtil.firstToUpper(sre.getName()) + "() /*TERMINAL String conversion*/;").append('\n');
w.append(indent + " if(s!=null) {").append('\n');
w.append(indent + " Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + lexerRuleName + "\", s);").append('\n');
w.append(indent + " getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent + " internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent + " getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
if(sre.ebnfOptional()) {
w.append(indent + " }").append('\n');
} else {
w.append(indent + " } else {").append('\n');
w.append(indent + " // non optional case, we return early since the property object is null").append('\n');
generateRejectStateCode(indent+ " ", gRule,altName, noCheck, w);
w.append(indent + " return false;").append('\n');
w.append(indent + " }").append('\n');
}
w.append(indent + " }").append('\n');
}
}
}
private static void generateNamedSubRuleElementCode(Writer w, String indent, UPNamedSubRuleElement sre,
RuleClass gRule, String altName, boolean noCheck) throws IOException {
w.append(indent+" // handling sub-rule " + sre.getId() + " with name '"+sre.getName()+"'").append('\n');
// if the sub-rule is a string or a string list, we need to manually consume the property.
Type subRuleType = gRule.getModel().propertyByName(gRule.nameWithUpper(), sre.getName()).get().getType();
String propName = "obj.get" + StringUtil.firstToUpper(sre.getName()) + "()";
if("String".equals(subRuleType.getName())) {
w.append(indent + " // we consume this sub-rule-property manually since it is a string or string list").append('\n');
String ruleType = "Formatter.RuleType.TERMINAL";
if(subRuleType.isArrayType()) {
String indexName = "prop" + StringUtil.firstToUpper(sre.getName()) + "ListIndex";
w.append(indent+" // list type: we set the current element index to it's maximum value so there won't be any consumable elements left").append('\n');
w.append(indent+" if(" + indexName+ ".get()" +" < " +propName+ ".size() ) {").append('\n');
w.append(indent+" String s = " + propName + ".get(" + indexName +".getAndInc()) /*TERMINAL String conversion*/;").append('\n');
w.append(indent+" if(s!=null) {").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + sre.getName() + "\", s);").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(s);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" }").append('\n');
} else {
String propStateName = "prop" + StringUtil.firstToUpper(sre.getName()) + "Used";
w.append(indent+" if(!" + propStateName + ".is()) { ").append('\n');
w.append(indent+" Formatter.RuleInfo ruleInfo = Formatter.RuleInfo.newRuleInfo(obj, " + ruleType + ", \"" + sre.getName() + "\", " + propName + ");").append('\n');
w.append(indent+" getUnparser().getFormatter().pre( unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" if(!ruleInfo.isConsumed()) {").append('\n');
w.append(indent+" internalW.print(" + propName + ");").append('\n');
w.append(indent+" }").append('\n');
w.append(indent+" getUnparser().getFormatter().post(unparser, ruleInfo, internalW);").append('\n');
w.append(indent+" // string type: we define the property as used/consumed").append('\n');
w.append(indent+" " + propStateName+".set(true);").append('\n');
if(!sre.ebnfOptional()) {
w.append(indent+" } else { // non-optional case, we return early").append('\n');
generateRejectStateCode(" ",gRule, altName, noCheck, w);
w.append(indent+" return false;").append('\n');
w.append(indent+" } ").append('\n');
} else {
w.append(indent+" } // optional case, we do not return early").append('\n');
}
}
} else {
// this is a normal sub-rule
w.append(indent+" getUnparser().unparse( " + propName +", internalW);").append('\n');
}
}
private static void generateSubRuleElementCode(Writer w, String altName, String indent, UPSubRuleElement sre, RuleClass gRule, boolean noCheck) throws IOException {
w.append(indent+" // handling sub-rule " + sre.getId()).append('\n');
if(!sre.ebnfOptional()&&!sre.ebnfZeroMany()) {
w.append(indent+" // this rule is not optional. we skip the rest of this alt if we can't match it.").append('\n');
w.append(indent+" if(!unparse" + altName + "SubRule" + sre.getId() + "( obj, internalW )) {").append('\n');
generateRejectStateCode(indent + " ", gRule, altName, noCheck, w);
w.append(indent+" return false;").append('\n');
w.append(indent+" }").append('\n');
} else {
w.append(indent+" // this rule is optional. we continue with the rest of this alt even if we can't match it.").append('\n');
w.append(indent + " unparse" + altName + "SubRule" + sre.getId() + "( obj, internalW );").append('\n');
}
}
}
// w.println(":type: unnamed-sub-rule");
//
// if(sre.ebnfOneMany()) {
// w.println("one-many: " + sre.ebnfOneMany());
// } else if(sre.ebnfZeroMany()) {
// w.println("zero-many: " + sre.ebnfZeroMany());
// } else if(sre.ebnfOne()) {
// w.println("one: " + sre.ebnfOne());
// } else if(sre.ebnfOptional()) {
// w.println("optional: " + sre.ebnfOptional());
// }