eu.mihosoft.vmf.vmftext.vmtemplates.model-converter.vm 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 ${packageName};
// java core imports
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
// antlr4 imports
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.ANTLRErrorStrategy;
// model imports
import ${modelPackageName}.*;
import static ${packageName}.${Util.firstToUpper($model.grammarName)}Parser.*;
public final class ${Util.firstToUpper($model.grammarName)}ModelParser {
public ${Util.firstToUpper($model.grammarName)}ModelParser() {
//
}
private final List errorListeners = new ArrayList<>();
private ANTLRErrorStrategy errorHandler;
private boolean consoleOutputDisabled;
public void setConsoleOutputDisabled(boolean state) {
this.consoleOutputDisabled = state;
}
public boolean isConsoleOutputDisabled() {
return this.consoleOutputDisabled;
}
public List getErrorListeners() {
return this.errorListeners;
}
public void setErrorHandler(ANTLRErrorStrategy errorHandler) {
this.errorHandler = errorHandler;
}
public ${Util.firstToUpper($model.grammarName)}Model parse(InputStream codeStream) throws IOException{
CharStream input = CharStreams.fromStream(codeStream);
${Util.firstToUpper($model.grammarName)}Lexer lexer = new ${Util.firstToUpper($model.grammarName)}Lexer(input);
for(ANTLRErrorListener l : errorListeners) {
lexer.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
lexer.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
CommonTokenStream tokens = new CommonTokenStream(lexer);
${Util.firstToUpper($model.grammarName)}Parser parser = new ${Util.firstToUpper($model.grammarName)}Parser(tokens);
if(errorHandler!=null) {
parser.setErrorHandler(errorHandler);
}
for(ANTLRErrorListener l : errorListeners) {
parser.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
parser.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
return parse(parser.${model.rootClass().name}());
}
public ${Util.firstToUpper($model.grammarName)}Model parse(File code) throws IOException{
try(FileInputStream codeStream = new FileInputStream(code)) {
return parse(codeStream);
}
}
public ${Util.firstToUpper($model.grammarName)}Model parse(String code) {
try(InputStream codeStream = new ByteArrayInputStream(code.getBytes(StandardCharsets.UTF_8.name()))) {
return parse(codeStream);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
public ${Util.firstToUpper($model.grammarName)}Model parse(${model.rootClass().nameWithUpper()}Context ctx) {
${Util.firstToUpper($model.grammarName)}Model model = ${Util.firstToUpper($model.grammarName)}Model.newInstance();
parse(model, ctx);
return model;
}
public ${Util.firstToUpper($model.grammarName)}Model parse(${Util.firstToUpper($model.grammarName)}Model model, InputStream codeStream) throws IOException{
CharStream input = CharStreams.fromStream(codeStream);
${Util.firstToUpper($model.grammarName)}Lexer lexer = new ${Util.firstToUpper($model.grammarName)}Lexer(input);
for(ANTLRErrorListener l : errorListeners) {
lexer.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
lexer.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
CommonTokenStream tokens = new CommonTokenStream(lexer);
${Util.firstToUpper($model.grammarName)}Parser parser = new ${Util.firstToUpper($model.grammarName)}Parser(tokens);
if(errorHandler!=null) {
parser.setErrorHandler(errorHandler);
}
for(ANTLRErrorListener l : errorListeners) {
parser.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
parser.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
return parse(model, parser.${model.rootClass().name}());
}
public ${Util.firstToUpper($model.grammarName)}Model parse(${Util.firstToUpper($model.grammarName)}Model model, File code) throws IOException{
try(FileInputStream codeStream = new FileInputStream(code)) {
return parse(model, codeStream);
}
}
public ${Util.firstToUpper($model.grammarName)}Model parse(${Util.firstToUpper($model.grammarName)}Model model, String code) {
try(InputStream codeStream = new ByteArrayInputStream(code.getBytes(StandardCharsets.UTF_8.name()))) {
return parse(model, codeStream);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
public ${Util.firstToUpper($model.grammarName)}Model parse(${Util.firstToUpper($model.grammarName)}Model model, ${model.rootClass().nameWithUpper()}Context ctx) {
model.setRoot(convert(ctx));
return model;
}
#foreach( $rcls in ${model.ruleClasses} )##foreach rule class
// ---- ${rcls.nameWithUpper()} CLASS PARSING BEGIN ----
#if(${rcls.getChildClasses().isEmpty()})## if rcls has no classes (we are no rule with labeled rule alts)
public ${rcls.nameWithUpper()} parse${rcls.nameWithUpper()}(InputStream codeStream) throws IOException {
return parse(${rcls.nameWithUpper()}.newInstance(), codeStream);
}
public ${rcls.nameWithUpper()} parse${rcls.nameWithUpper()}(File code) throws IOException {
return parse(${rcls.nameWithUpper()}.newInstance(), code);
}
public ${rcls.nameWithUpper()} parse${rcls.nameWithUpper()}(String code) {
return parse(${rcls.nameWithUpper()}.newInstance(), code);
}
#else
public ${rcls.nameWithUpper()} parse${rcls.nameWithUpper()}(InputStream codeStream) throws IOException {
CharStream input = CharStreams.fromStream(codeStream);
${Util.firstToUpper($model.grammarName)}Lexer lexer = new ${Util.firstToUpper($model.grammarName)}Lexer(input);
for(ANTLRErrorListener l : errorListeners) {
lexer.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
lexer.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
CommonTokenStream tokens = new CommonTokenStream(lexer);
${Util.firstToUpper($model.grammarName)}Parser parser = new ${Util.firstToUpper($model.grammarName)}Parser(tokens);
if(errorHandler!=null) {
parser.setErrorHandler(errorHandler);
}
for(ANTLRErrorListener l : errorListeners) {
parser.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
parser.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
#if(!${rcls.getChildClasses().isEmpty()})## if rcls has child classes
// we use a delegating convert
return convert(parser.${rcls.nameWithLower()}());
#elseif(!${rcls.getSuperClass()})## if rcls has super-class
// we use a non-delegating convert ${rcls.getSuperClass().nameWithLower()}
return convert(parser.${rcls.nameWithLower()}());
#else
// we use a delegating convert (we are the labeled alt)
return (${rcls.nameWithUpper()}) convert(parser.${rcls.getSuperClass().nameWithLower()}());
#end## if rcls has child classes
}
public ${rcls.nameWithUpper()} parse${rcls.nameWithUpper()}(File code) throws IOException {
try(FileInputStream codeStream = new FileInputStream(code)) {
return parse${rcls.nameWithUpper()}(codeStream);
}
}
public ${rcls.nameWithUpper()} parse${rcls.nameWithUpper()}(String code) {
try(InputStream codeStream = new ByteArrayInputStream(code.getBytes(StandardCharsets.UTF_8.name()))) {
return parse${rcls.nameWithUpper()}(codeStream);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
#end## if rcls has no classes (we are no rule with labeled rule alts)
public ${rcls.nameWithUpper()} parse(${rcls.nameWithUpper()} ${rcls.nameWithLower()}, InputStream codeStream) throws IOException {
CharStream input = CharStreams.fromStream(codeStream);
${Util.firstToUpper($model.grammarName)}Lexer lexer = new ${Util.firstToUpper($model.grammarName)}Lexer(input);
for(ANTLRErrorListener l : errorListeners) {
lexer.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
lexer.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
CommonTokenStream tokens = new CommonTokenStream(lexer);
${Util.firstToUpper($model.grammarName)}Parser parser = new ${Util.firstToUpper($model.grammarName)}Parser(tokens);
if(errorHandler!=null) {
parser.setErrorHandler(errorHandler);
}
for(ANTLRErrorListener l : errorListeners) {
parser.addErrorListener(l);
}
if(isConsoleOutputDisabled()) {
parser.removeErrorListener(org.antlr.v4.runtime.ConsoleErrorListener.INSTANCE);
}
#if(!${rcls.getChildClasses().isEmpty()})## if rcls has child classes
ParserRuleContext parsedCtx = parser.${rcls.nameWithLower()}();
// check whether parser result matches expected rule type
if(!(parsedCtx instanceof ${rcls.nameWithUpper()}Context)) {
throw new ClassCastException("Parser found a '"+parsedCtx.getClass().getSimpleName()+"' instead of the requested '${rcls.nameWithUpper()}Context'.");
}
// we use a delegating convert
return convert((${rcls.nameWithUpper()}Context)parsedCtx, ${rcls.nameWithLower()});
#elseif(!${rcls.getSuperClass()})## if rcls has super-class
ParserRuleContext parsedCtx = parser.${rcls.nameWithLower()}();
// check whether parser result matches expected rule type
if(!(parsedCtx instanceof ${rcls.nameWithUpper()}Context)) {
throw new ClassCastException("Parser found a '"+parsedCtx.getClass().getSimpleName()+"' instead of the requested '${rcls.nameWithUpper()}Context'.");
}
// we use a non-delegating convert
return convert((${rcls.nameWithUpper()}Context)parsedCtx, ${rcls.nameWithLower()});
#else
ParserRuleContext parsedCtx = parser.${rcls.getSuperClass().nameWithLower()}();
// check whether parser result matches expected rule type
if(!(parsedCtx instanceof ${rcls.nameWithUpper()}Context)) {
throw new ClassCastException("Parser found a '"+parsedCtx.getClass().getSimpleName()+"' instead of the requested '${rcls.nameWithUpper()}Context'.");
}
// we use a delegating convert (we are the labeled alt)
return (${rcls.nameWithUpper()}) convert((${rcls.getSuperClass().nameWithUpper()}Context)parsedCtx, ${rcls.nameWithLower()});
#end## if rcls has child classes
}
public ${rcls.nameWithUpper()} parse(${rcls.nameWithUpper()} ${rcls.nameWithLower()}, File code) throws IOException{
try(FileInputStream codeStream = new FileInputStream(code)) {
return parse(${rcls.nameWithLower()}, codeStream);
}
}
public ${rcls.nameWithUpper()} parse(${rcls.nameWithUpper()} ${rcls.nameWithLower()}, String code) {
try(InputStream codeStream = new ByteArrayInputStream(code.getBytes(StandardCharsets.UTF_8.name()))) {
return parse(${rcls.nameWithLower()}, codeStream);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
// ---- ${rcls.nameWithUpper()} CLASS PARSING END ----
#end##foreach rule class
#foreach( $rcls in ${model.ruleClasses} )
#if($rcls.getChildClasses().isEmpty())## if rcls has no child classes
private ${rcls.nameWithUpper()} convert(${rcls.nameWithUpper()}Context ctx) {
return convert(ctx, ${rcls.nameWithUpper()}.newInstance());
}
private ${rcls.nameWithUpper()} convert(${rcls.nameWithUpper()}Context ctx, ${rcls.nameWithUpper()} ${rcls.nameWithLower()}) {
// ----------------------------------------
// Initializing Rule Instance
// ----------------------------------------
// create model instance
// ${rcls.nameWithUpper()} ${rcls.nameWithLower()} = ${rcls.nameWithUpper()}.newInstance();
// set code range (converted from antlr start and stop tokens)
${rcls.nameWithLower()}.setCodeRange(ctxToCodeRange(ctx));
// ----------------------------------------
// Converting Properties
// ----------------------------------------
#foreach( $p in $rcls.properties )
#if($p.type.ruleType)
#if($p.type.arrayType)
// convert elements of rule attribute $rcls.nameWithUpper().$p.nameWithLower()
List<${p.type.asJavaTypeNameNoCollections()}> convertedElements$p.nameWithUpper() = ctx.${p.nameWithLower()}.stream().
map(entry->convert(entry)).collect(Collectors.toList());
// assign elements to model entity
${rcls.nameWithLower()}.get${p.nameWithUpper()}().addAll(convertedElements$p.nameWithUpper());
#else
// convert and assign rule attribute $rcls.nameWithUpper().$p.nameWithLower()
// if it is present
if(ctx.${p.nameWithLower()}!=null) {
${rcls.nameWithLower()}.set${p.nameWithUpper()}(convert(ctx.${p.nameWithLower()}));
}
#end## if array type
#else## if rule type
#if($p.type.arrayType)
// convert elements of rule attribute $rcls.nameWithUpper().$p.nameWithLower()
// proper conversion of primitives, e.g., lexer rules to int, double etc. depends on type mappings
// pseudo-code:
//
// if p.type needs conversion then
// typemapping = getTypeMappings(p.type.antlrRule)
// List convertedElements = ... map(entry->typemapping.convert(entry)) ...
//
List<$model.getTypeMappings().targetTypeNameOfMapping($rcls.nameWithUpper(), $p.type.getAntlrRuleName())> convertedElements$p.nameWithUpper() =
ctx.${p.nameWithLower()}.stream().map(entry->${model.getTypeMappings().conversionCodeOfMappingStringToType($rcls.nameWithUpper(), $p.type.getAntlrRuleName())}).
collect(Collectors.toList());
// FALLBACK: for lexer rules without conversion rules
// List convertedElements$p.nameWithUpper() = ctx.${p.nameWithLower()}.stream().
// map(entry->entry.getText()).collect(Collectors.toList());
// assign elements to model entity
${rcls.nameWithLower()}.get${p.nameWithUpper()}().addAll(convertedElements$p.nameWithUpper());
#else## if array type
// TODO proper conversion of primitives, e.g., lexer rules to int, double etc.
// convert and assign rule attribute $rcls.nameWithUpper().$p.nameWithLower()
// if it is present
if(ctx.${p.nameWithLower()}!=null) {
// FALLBACK:
// ${rcls.nameWithLower()}.set${p.nameWithUpper()}(ctx.${p.nameWithLower()}.getText());
Token entry = ctx.${p.nameWithLower()};
${rcls.nameWithLower()}.set${p.nameWithUpper()}(${model.getTypeMappings().conversionCodeOfMappingStringToType($rcls.nameWithUpper(), $p.type.getAntlrRuleName())});
}
#end## if array type
#end## if rule type
#end## foreach property in rule class
return ${rcls.nameWithLower()};
}
#else## if rcls has no child classes
private ${rcls.nameWithUpper()} convert(${rcls.nameWithUpper()}Context ctx) {
// ----------------------------------------
// Delegating to labeled alternatives:
//
// -> rules with alt-labels are never used
// directly but always delegate to child
// rules which inherit from the rule
// ----------------------------------------
// -- BEGIN: delegation
#foreach( $r in $rcls.childClasses )
if(ctx instanceof ${r.nameWithUpper()}Context) {
return convert((${r.nameWithUpper()}Context)ctx);
}
#end## for-each child rule
// -- END: delegation
// WARNING: 10.11.2017 This should not happen:
// The current design does not use rules with alt labeled directly but
// only sub classes
// TODO: 10.11.2017 enforce validation/testing for this specific case
System.err.println("VMF[WARNING]: rule-class '${r.nameWithUpper()}' must delegate to child classes.");
return null;
}
private ${rcls.nameWithUpper()} convert(${rcls.nameWithUpper()}Context ctx, ${rcls.nameWithUpper()} ${rcls.nameWithLower()}) {
// ----------------------------------------
// Delegating to labeled alternatives:
//
// -> rules with alt-labels are never used
// directly but always delegate to child
// rules which inherit from the rule
// ----------------------------------------
// -- BEGIN: delegation
#foreach( $r in $rcls.childClasses )
if(ctx instanceof ${r.nameWithUpper()}Context) {
return convert((${r.nameWithUpper()}Context)ctx, (${r.nameWithUpper()}) ${rcls.nameWithLower()});
}
#end## for-each child rule
// -- END: delegation
// WARNING: 10.11.2017 This should not happen:
// The current design does not use rules with alt labeled directly but
// only sub classes
// TODO: 10.11.2017 enforce validation/testing for this specific case
System.err.println("VMF[WARNING]: rule-class '${r.nameWithUpper()}' must delegate to child classes.");
return null;
}
#end## if rcls has no child classes
#end## foreach rule class
// --------------------------------------------------------------------------------
// UTILITY METHODS
// --------------------------------------------------------------------------------
public static CodeLocation tokenToCodeLocationStart(Token t) {
return CodeLocation.newBuilder().
withIndex(t.getStartIndex()).
withCharPosInLine(t.getCharPositionInLine()).withLine(t.getLine()).build();
}
public static CodeLocation tokenToCodeLocationStop(Token t, ParserRuleContext ctx) {
int startIndex = t.getStartIndex();
int stopIndex = t.getStopIndex();
int length = stopIndex - startIndex;
return CodeLocation.newBuilder().
withIndex(stopIndex).
withCharPosInLine(t.getCharPositionInLine()+length).
withLine(/*TODO 14.02.2018 does not work for multiple-line-tokens*/t.getLine()).build();
}
public static CodeRange ctxToCodeRange(ParserRuleContext ctx) {
// consider for stop line: https://stackoverflow.com/a/17487805
CodeRange.Builder builder = CodeRange.newBuilder().withStart(tokenToCodeLocationStart(ctx.start));
CodeLocation start = tokenToCodeLocationStart(ctx.start);
CodeLocation stop;
if(ctx.stop==null) {
// if no stop is present use start token
stop = tokenToCodeLocationStop(ctx.start,ctx);
builder.withStop(stop);
} else {
stop = tokenToCodeLocationStop(ctx.stop,ctx);
builder.withStop(stop);
}
return builder.withLength(stop.getIndex()-start.getIndex()+1/*+1 because of inclusive vs. exclusive*/).build();
}
// TODO remove this class if compile time conversion works
/*
// --------------------------------------------------------------------------------
// TYPE CONVERSION UTILITY CLASS
// --------------------------------------------------------------------------------
public TypeConversionRegistry getTypeConversionRegistry() {
return typeConversionRegistry;
}
private static class TypeConversionRegistryImpl implements TypeConversionRegistry {
private final Map> converterMap = new HashMap<>();
@Override
public void registerConverter(TypeConverter t, String ruleClsName, String propertyRuleName, String outputTypeName) {
getConverterMap(ruleClsName).put(propertyRuleName,t);
}
@Override
public void registerConverter(TypeConverter t, String propertyRuleName, String outputTypeName) {
getConverterMap("").put(propertyRuleName,t);
}
@Override
public Object convert(String ruleClsName, String propertyRuleName, Object obj) {
System.out.println("> conversion requested for '"+ruleClsName+"::"+propertyRuleName + "'");
Map converters =
getConverterMap(ruleClsName);
if(converters.containsKey(propertyRuleName)) {
System.out.println(" -> found converter registered for property type '"+propertyRuleName+"'.");
TypeConverter tC = converters.get(propertyRuleName);
return tC.convert(obj);
}
System.out.println(" -> looking for global converter for property type '"+propertyRuleName+"'.");
Map globalConverters =
getConverterMap("");
if(globalConverters.containsKey(propertyRuleName)) {
System.out.println("-> found global converter for property type '"+propertyRuleName+"'.");
return globalConverters.get(propertyRuleName);
}
throw new RuntimeException("Cannot find requested type conversion for'"
+ruleClsName+"::"+propertyRuleName + "'");
}
private Map getConverterMap(String ruleClassName) {
Map result = converterMap.get(ruleClassName);
if(result==null) {
result = new HashMap();
converterMap.put(ruleClassName, result);
}
return result;
}
} // end TypeConversionRegistryImpl
*/
}