From 8be383305b8cd768a34890b0e0e27b2663fc13b7 Mon Sep 17 00:00:00 2001 From: homedirectory Date: Wed, 7 Feb 2024 19:52:44 +0200 Subject: [PATCH 1/3] Ensure that keywords aren't used for variable names This corrects the generation of variable names corresponding to terminal parameters, such as Class or Enum. Previous implementation could erronously generate code such as: `java.lang.Enum enum = ...;` or `Type method(Class class)` --- .../grammar/types/ClassParameter.java | 20 ++++++------------- .../technion/cs/fling/namers/NaiveNamer.java | 8 ++++++++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/il/ac/technion/cs/fling/internal/grammar/types/ClassParameter.java b/src/main/java/il/ac/technion/cs/fling/internal/grammar/types/ClassParameter.java index 409f3010..f4fa42da 100644 --- a/src/main/java/il/ac/technion/cs/fling/internal/grammar/types/ClassParameter.java +++ b/src/main/java/il/ac/technion/cs/fling/internal/grammar/types/ClassParameter.java @@ -1,6 +1,8 @@ package il.ac.technion.cs.fling.internal.grammar.types; + +import static il.ac.technion.cs.fling.namers.NaiveNamer.lowerCamelCase; +import static il.ac.technion.cs.fling.namers.NaiveNamer.unreservedName; import static java.util.Objects.requireNonNull; -import il.ac.technion.cs.fling.namers.NaiveNamer; // TODO allow primitive types. public class ClassParameter implements StringTypeParameter { public final Class parameterClass; @@ -11,7 +13,9 @@ public ClassParameter(final Class parameterClass) { return parameterClass.getCanonicalName(); } @Override public String baseParameterName() { - return unPrimitiveTypeSimple(NaiveNamer.lowerCamelCase(parameterClass.getSimpleName())); + if (parameterClass.isPrimitive()) + return parameterClass.getSimpleName().substring(0, 1); + return unreservedName(lowerCamelCase(parameterClass.getSimpleName())); } @Override public int hashCode() { return parameterClass.hashCode(); @@ -39,16 +43,4 @@ public static String unPrimitiveType(final String typeName) { void.class.getName().equals(typeName) ? Void.class.getCanonicalName() : // typeName; } - public static String unPrimitiveTypeSimple(final String typeName) { - return byte.class.getName().equals(typeName) ? "b" : // - short.class.getName().equals(typeName) ? "s" : // - int.class.getName().equals(typeName) ? "i" : // - long.class.getName().equals(typeName) ? "l" : // - float.class.getName().equals(typeName) ? "f" : // - double.class.getName().equals(typeName) ? "d" : // - boolean.class.getName().equals(typeName) ? "b" : // - char.class.getName().equals(typeName) ? "c" : // - void.class.getName().equals(typeName) ? "v" : // - typeName; - } } diff --git a/src/main/java/il/ac/technion/cs/fling/namers/NaiveNamer.java b/src/main/java/il/ac/technion/cs/fling/namers/NaiveNamer.java index a85b3b76..e33e438c 100644 --- a/src/main/java/il/ac/technion/cs/fling/namers/NaiveNamer.java +++ b/src/main/java/il/ac/technion/cs/fling/namers/NaiveNamer.java @@ -23,6 +23,9 @@ import il.ac.technion.cs.fling.internal.grammar.rules.Component; import il.ac.technion.cs.fling.internal.grammar.rules.Constants; import il.ac.technion.cs.fling.internal.grammar.rules.Variable; + +import javax.lang.model.SourceVersion; + public class NaiveNamer implements Namer { private final Map astChildrenCounter = new HashMap<>(); private final Map notationsChildrenCounter = new HashMap<>(); @@ -168,6 +171,11 @@ public static String getNameFromBase(final String baseName, final Map Date: Thu, 8 Feb 2024 16:39:30 +0200 Subject: [PATCH 2/3] Refactor the AST visitor to be useful AST visitor generation was producing visitors that were hardly useful: 1. `visit` methods were final which prevented implementations from controlling the descending behaviour 2. `whileVisiting` methods were hardly useful due to point 1 Consider the task of wrapping a parent AST node in parentheses, including its children. This could not be achieved using previous AST visitors. This change makes visitors be generated in the fashion of javax.lang.model.util.ElementScanner. It allows implementations to control the descent like so: void visit(ParentNode node) { print("("); // before visiting children Visitor.super.visit(node); // descend print(")"); // after having visited children } --- .../fling/adapters/JavaASTVisitorAdapter.java | 142 +++++++++--------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/src/main/java/il/ac/technion/cs/fling/adapters/JavaASTVisitorAdapter.java b/src/main/java/il/ac/technion/cs/fling/adapters/JavaASTVisitorAdapter.java index 075619f9..f25c6394 100644 --- a/src/main/java/il/ac/technion/cs/fling/adapters/JavaASTVisitorAdapter.java +++ b/src/main/java/il/ac/technion/cs/fling/adapters/JavaASTVisitorAdapter.java @@ -1,117 +1,117 @@ package il.ac.technion.cs.fling.adapters; -import static java.util.stream.Collectors.joining; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; + import il.ac.technion.cs.fling.internal.compiler.Namer; -import il.ac.technion.cs.fling.internal.compiler.ast.nodes.ASTCompilationUnitNode; -import il.ac.technion.cs.fling.internal.compiler.ast.nodes.AbstractClassNode; -import il.ac.technion.cs.fling.internal.compiler.ast.nodes.ClassNode; -import il.ac.technion.cs.fling.internal.compiler.ast.nodes.ConcreteClassNode; -import il.ac.technion.cs.fling.internal.compiler.ast.nodes.FieldNode; +import il.ac.technion.cs.fling.internal.compiler.ast.nodes.*; import il.ac.technion.cs.fling.internal.grammar.rules.Variable; import il.ac.technion.cs.fling.internal.grammar.sententials.quantifiers.JavaCompatibleQuantifier; import il.ac.technion.cs.fling.namers.NaiveNamer; -/** Java adapter printing AST visitor class given AST type definitions. + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; + +/** + * Java adapter printing AST visitor class given AST type definitions. * - * @author Ori Roth */ -@SuppressWarnings("static-method") public class JavaASTVisitorAdapter { + * @author Ori Roth + */ +@SuppressWarnings("static-method") +public class JavaASTVisitorAdapter { private final String astClassName; private final Namer namer; private final String packageName; + public JavaASTVisitorAdapter(final String packageName, final String astClassName, final Namer namer) { this.packageName = packageName; this.astClassName = astClassName; this.namer = namer; } + public String printASTVisitorClass(final ASTCompilationUnitNode compilationUnit) { - return String.format("public static class %s{%s%s}", // - VISITOR_CLASS_NAME, // - compilationUnit.classes.stream() // - .map(this::printVisitMethod) // - .collect(joining()), // - compilationUnit.classes.stream() // - .filter(ClassNode::isConcrete) // - .map(ClassNode::asConcrete) // - .map(this::printWhileVisitingMethod) // - .collect(joining())); + return format("public interface %s{%s}", // + VISITOR_CLASS_NAME, // + compilationUnit.classes.stream() // + .map(this::printVisitMethod) // + .collect(joining())); } + public String printVisitMethod(final AbstractClassNode clazz) { final Variable source = clazz.source; final String parameterName = getNodeParameterName(source); - return String.format("public final void visit(%s %s){%s}", // - getASTVariableClassName(source), // - parameterName, // - printVisitMethodBody(clazz, parameterName)); + return format("default void visit(%s %s){%s}", // + getASTVariableClassName(source), // + parameterName, // + printVisitMethodBody(clazz, parameterName)); } + public String printVisitMethod(final ClassNode clazz) { return clazz.isAbstract() ? // - printVisitMethod(clazz.asAbstract()) : // - printVisitMethod(clazz.asConcrete()); + printVisitMethod(clazz.asAbstract()) : // + printVisitMethod(clazz.asConcrete()); } + public String printVisitMethod(final ConcreteClassNode clazz) { final Variable source = clazz.source; final String parameterName = getNodeParameterName(source); - return String.format("public final void visit(%s %s){%s}", // - getASTVariableClassName(source), // - parameterName, // - printVisitMethodBody(clazz, parameterName)); - } - public String printWhileVisitingMethod(final ConcreteClassNode clazz) { - final Variable source = clazz.source; - final String parameterName = getNodeParameterName(source); - return String.format("public void whileVisiting(%s %s)throws %s{}", // - getASTVariableClassName(source), // - parameterName, // - Exception.class.getCanonicalName()); + return format("default void visit(%s %s){%s}", // + getASTVariableClassName(source), // + parameterName, // + printVisitMethodBody(clazz, parameterName)); } + private String getASTVariableClassName(final Variable variable) { - return String.format("%s.%s.%s", // - packageName, // - astClassName, // - namer.getASTClassName(variable)); + return format("%s.%s.%s", // + packageName, // + astClassName, // + namer.getASTClassName(variable)); } + private String getNodeParameterName(final Variable variable) { return NaiveNamer.lowerCamelCase(variable.name()); } + private String printVisitMethodBody(final AbstractClassNode clazz, final String parameterName) { return clazz.children.stream() // - .map(child -> String.format("if(%s instanceof %s)%s", // - parameterName, // - getASTVariableClassName(child.source), // - variableVisitingStatement(child.source, parameterName))) // - .collect(joining("else ")); + .map(child -> format("if(%s instanceof %s)%s", // + parameterName, // + getASTVariableClassName(child.source), // + variableVisitingStatement(child.source, parameterName))) // + .collect(joining("else ")); } + private String printVisitMethodBody(final ConcreteClassNode clazz, final String parameterName) { final StringBuilder $ = new StringBuilder(); final Map usedNames = new LinkedHashMap<>(); - $.append(String.format("try{this.whileVisiting(%s);}catch(%s __){__.printStackTrace();}", // - parameterName, // - Exception.class.getCanonicalName())); clazz.fields.stream() // - .map(FieldNode::source) // - .forEach(source -> { - assert !source.isQuantifier() || source.getClass() - .isAnnotationPresent(JavaCompatibleQuantifier.class) : "BNF uses a non-Java-compatible notation"; - }); + .map(FieldNode::source) // + .forEach(source -> { + assert !source.isQuantifier() || source.getClass() + .isAnnotationPresent(JavaCompatibleQuantifier.class) : "BNF uses a non-Java-compatible notation"; + }); clazz.fields.stream() // - .map(FieldNode::getInferredFieldFragments) // - .flatMap(List::stream) // - .map(field -> field.visitingStatement(// - this::variableVisitingStatement, // - String.format("%s.%s", // - parameterName, // - field.parameterName), // - () -> NaiveNamer.getNameFromBase("_x_", usedNames))) - .filter(Objects::nonNull) // - .forEach($::append); + .map(FieldNode::getInferredFieldFragments) // + .flatMap(List::stream) // + .map(field -> field.visitingStatement(// + this::variableVisitingStatement, // + format("%s.%s", // + parameterName, // + field.parameterName), // + () -> NaiveNamer.getNameFromBase("_x_", usedNames))) + .filter(Objects::nonNull) // + .forEach($::append); return $.toString(); } + private String variableVisitingStatement(final Variable variable, final String access) { - return String.format("{visit((%s)%s);}", // - getASTVariableClassName(variable), // - access); + return format("{visit((%s)%s);}", // + getASTVariableClassName(variable), // + access); } + private static final String VISITOR_CLASS_NAME = "Visitor"; + } From 98c66664c11ec962a13e03fc0713bfc80f2b423f Mon Sep 17 00:00:00 2001 From: homedirectory Date: Thu, 15 Feb 2024 13:10:13 +0200 Subject: [PATCH 3/3] Allow token parameterisation with multiple distinct parameters --- .../internal/grammar/rules/Terminal.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/il/ac/technion/cs/fling/internal/grammar/rules/Terminal.java b/src/main/java/il/ac/technion/cs/fling/internal/grammar/rules/Terminal.java index 1766690b..6c3505ec 100644 --- a/src/main/java/il/ac/technion/cs/fling/internal/grammar/rules/Terminal.java +++ b/src/main/java/il/ac/technion/cs/fling/internal/grammar/rules/Terminal.java @@ -3,6 +3,9 @@ import il.ac.technion.cs.fling.internal.grammar.types.VarargsClassParameter; import il.ac.technion.cs.fling.internal.grammar.types.VarargsVariableTypeParameter; import il.ac.technion.cs.fling.internal.grammar.types.VariableTypeParameter; + +import java.util.Arrays; + /** Language terminal symbol, never occurs in grammar; * * @see Token @@ -11,12 +14,12 @@ public interface Terminal extends TempSymbol { @Override default Token normalize() { return new Token(this); } - /** Associate parameter with this terminal + /** Associate parameters with this terminal * - * @param clazz parameter type + * @param types parameter types * @return newly created token */ - default Token with(final Class clazz) { - return new Token(this, new ClassParameter(clazz)); + default Token with(final Class... types) { + return new Token(this, Arrays.stream(types).map(ClassParameter::new).toArray(ClassParameter[]::new)); } /** Assign parameter varargs to this terminal. * @@ -25,12 +28,12 @@ default Token with(final Class clazz) { default Token many(final Class parameterClass) { return new Token(this, new VarargsClassParameter(parameterClass)); } - /** Assign variable as parameter to this terminal. + /** Assign variables as parameter to this terminal. * - * @param variable parameter variable + * @param variables parameter variables * @return newly created token */ - default Token with(final Variable variable) { - return new Token(this, new VariableTypeParameter(variable)); + default Token with(final Variable... variables) { + return new Token(this, Arrays.stream(variables).map(VariableTypeParameter::new).toArray(VariableTypeParameter[]::new)); } /** Assign variable as varargs parameter to this terminal. *