8344942: Template-Based Testing Framework
Co-authored-by: Tobias Hartmann <thartmann@openjdk.org> Co-authored-by: Tobias Holenstein <tholenstein@openjdk.org> Co-authored-by: Theo Weidmann <tweidmann@openjdk.org> Co-authored-by: Roberto Castañeda Lozano <rcastanedalo@openjdk.org> Co-authored-by: Christian Hagedorn <chagedorn@openjdk.org> Co-authored-by: Manuel Hässig <mhaessig@openjdk.org> Reviewed-by: chagedorn, mhaessig, rcastanedalo
This commit is contained in:
parent
09ec4de74d
commit
248341d372
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
record AddNameToken(Name name) implements Token {}
|
51
test/hotspot/jtreg/compiler/lib/template_framework/Code.java
Normal file
51
test/hotspot/jtreg/compiler/lib/template_framework/Code.java
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class collects code, i.e. {@link String}s or {@link List}s of {@link String}s.
|
||||
* All the {@link String}s are later collected in a {@link StringBuilder}. If we used a {@link StringBuilder}
|
||||
* directly to collect the {@link String}s, we could not as easily insert code at an "earlier" position, i.e.
|
||||
* reaching out to a {@link Hook#anchor}.
|
||||
*/
|
||||
sealed interface Code permits Code.Token, Code.CodeList {
|
||||
|
||||
record Token(String s) implements Code {
|
||||
@Override
|
||||
public void renderTo(StringBuilder builder) {
|
||||
builder.append(s);
|
||||
}
|
||||
}
|
||||
|
||||
record CodeList(List<Code> list) implements Code {
|
||||
@Override
|
||||
public void renderTo(StringBuilder builder) {
|
||||
list.forEach(code -> code.renderTo(builder));
|
||||
}
|
||||
}
|
||||
|
||||
void renderTo(StringBuilder builder);
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link CodeFrame} represents a frame (i.e. scope) of code, appending {@link Code} to the {@code 'codeList'}
|
||||
* as {@link Token}s are rendered, and adding names to the {@link NameSet}s with {@link Template#addStructuralName}/
|
||||
* {@link Template#addDataName}. {@link Hook}s can be added to a frame, which allows code to be inserted at that
|
||||
* location later. When a {@link Hook} is {@link Hook#anchor}ed, it separates the Template into an outer and inner
|
||||
* {@link CodeFrame}, ensuring that names that are added inside the inner frame are only available inside that frame.
|
||||
*
|
||||
* <p>
|
||||
* On the other hand, each {@link TemplateFrame} represents the frame (or scope) of exactly one use of a
|
||||
* Template.
|
||||
*
|
||||
* <p>
|
||||
* For simple Template nesting, the {@link CodeFrame}s and {@link TemplateFrame}s overlap exactly.
|
||||
* However, when using {@link Hook#insert}, we simply nest {@link TemplateFrame}s, going further "in",
|
||||
* but we jump to an outer {@link CodeFrame}, ensuring that we insert {@link Code} at the outer frame,
|
||||
* and operating on the names of the outer frame. Once the {@link Hook#insert}ion is complete, we jump
|
||||
* back to the caller {@link TemplateFrame} and {@link CodeFrame}.
|
||||
*/
|
||||
class CodeFrame {
|
||||
public final CodeFrame parent;
|
||||
private final List<Code> codeList = new ArrayList<>();
|
||||
private final Map<Hook, Code.CodeList> hookCodeLists = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The {@link NameSet} is used for variable and fields etc.
|
||||
*/
|
||||
private final NameSet names;
|
||||
|
||||
private CodeFrame(CodeFrame parent, boolean isTransparentForNames) {
|
||||
this.parent = parent;
|
||||
if (parent == null) {
|
||||
// NameSet without any parent.
|
||||
this.names = new NameSet(null);
|
||||
} else if (isTransparentForNames) {
|
||||
// We use the same NameSet as the parent - makes it transparent.
|
||||
this.names = parent.names;
|
||||
} else {
|
||||
// New NameSet, to make sure we have a nested scope for the names.
|
||||
this.names = new NameSet(parent.names);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a base frame, which has no {@link #parent}.
|
||||
*/
|
||||
public static CodeFrame makeBase() {
|
||||
return new CodeFrame(null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a normal frame, which has a {@link #parent} and which defines an inner
|
||||
* {@link NameSet}, for the names that are generated inside this frame. Once this
|
||||
* frame is exited, the name from inside this frame are not available anymore.
|
||||
*/
|
||||
public static CodeFrame make(CodeFrame parent) {
|
||||
return new CodeFrame(parent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a special frame, which has a {@link #parent} but uses the {@link NameSet}
|
||||
* from the parent frame, allowing {@link Template#addDataName}/
|
||||
* {@link Template#addStructuralName} to persist in the outer frame when the current frame
|
||||
* is exited. This is necessary for {@link Hook#insert}, where we would possibly want to
|
||||
* make field or variable definitions during the insertion that are not just local to the
|
||||
* insertion but affect the {@link CodeFrame} that we {@link Hook#anchor} earlier and are
|
||||
* now {@link Hook#insert}ing into.
|
||||
*/
|
||||
public static CodeFrame makeTransparentForNames(CodeFrame parent) {
|
||||
return new CodeFrame(parent, true);
|
||||
}
|
||||
|
||||
void addString(String s) {
|
||||
codeList.add(new Code.Token(s));
|
||||
}
|
||||
|
||||
void addCode(Code code) {
|
||||
codeList.add(code);
|
||||
}
|
||||
|
||||
void addHook(Hook hook) {
|
||||
if (hasHook(hook)) {
|
||||
// This should never happen, as we add a dedicated CodeFrame for each hook.
|
||||
throw new RuntimeException("Internal error: Duplicate Hook in CodeFrame: " + hook.name());
|
||||
}
|
||||
hookCodeLists.put(hook, new Code.CodeList(new ArrayList<>()));
|
||||
}
|
||||
|
||||
private boolean hasHook(Hook hook) {
|
||||
return hookCodeLists.containsKey(hook);
|
||||
}
|
||||
|
||||
CodeFrame codeFrameForHook(Hook hook) {
|
||||
CodeFrame current = this;
|
||||
while (current != null) {
|
||||
if (current.hasHook(hook)) {
|
||||
return current;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void addName(Name name) {
|
||||
names.add(name);
|
||||
}
|
||||
|
||||
Name sampleName(NameSet.Predicate predicate) {
|
||||
return names.sample(predicate);
|
||||
}
|
||||
|
||||
int countNames(NameSet.Predicate predicate) {
|
||||
return names.count(predicate);
|
||||
}
|
||||
|
||||
boolean hasAnyNames(NameSet.Predicate predicate) {
|
||||
return names.hasAny(predicate);
|
||||
}
|
||||
|
||||
List<Name> listNames(NameSet.Predicate predicate) {
|
||||
return names.toList(predicate);
|
||||
}
|
||||
|
||||
Code getCode() {
|
||||
return new Code.CodeList(codeList);
|
||||
}
|
||||
}
|
227
test/hotspot/jtreg/compiler/lib/template_framework/DataName.java
Normal file
227
test/hotspot/jtreg/compiler/lib/template_framework/DataName.java
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link DataName}s represent things like fields and local variables, and can be added to the local
|
||||
* scope with {@link Template#addDataName}, and accessed with {@link Template#dataNames}, to
|
||||
* count, list or even sample random {@link DataName}s. Every {@link DataName} has a {@link DataName.Type},
|
||||
* so that sampling can be restricted to these types.
|
||||
*
|
||||
* <p>
|
||||
* For method and class names and alike, there are the analogous {@link StructuralName}s.
|
||||
*
|
||||
* @param name The {@link String} name used in code.
|
||||
* @param type The type of the {@link DataName}.
|
||||
* @param mutable Defines if the {@link DataName} is considered mutable or immutable.
|
||||
* @param weight The weight of the {@link DataName}, it corresponds to the probability of choosing this
|
||||
* {@link DataName} when sampling later on.
|
||||
*/
|
||||
public record DataName(String name, DataName.Type type, boolean mutable, int weight) implements Name {
|
||||
|
||||
/**
|
||||
* {@link Mutability} defines the possible states of {@link DataName}s, or the
|
||||
* desired state when filtering.
|
||||
*/
|
||||
public enum Mutability {
|
||||
/**
|
||||
* Used for mutable fields and variables, i.e. writing is allowed.
|
||||
*/
|
||||
MUTABLE,
|
||||
/**
|
||||
* Used for immutable fields and variables, i.e. writing is not allowed,
|
||||
* for example because the field or variable is final.
|
||||
*/
|
||||
IMMUTABLE,
|
||||
/**
|
||||
* When filtering, we sometimes want to indicate that we accept
|
||||
* mutable and immutable fields and variables, for example when
|
||||
* we are only reading, the mutability state does not matter.
|
||||
*/
|
||||
MUTABLE_OR_IMMUTABLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DataName}.
|
||||
*/
|
||||
public DataName {
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface for the type of a {@link DataName}.
|
||||
*/
|
||||
public interface Type extends Name.Type {
|
||||
/**
|
||||
* The name of the type, that can be used in code.
|
||||
*
|
||||
* @return The {@link String} representation of the type, that can be used in code.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Defines the subtype relationship with other types, which is used to filter {@link DataName}s
|
||||
* in {@link FilteredSet#exactOf}, {@link FilteredSet#subtypeOf}, and {@link FilteredSet#supertypeOf}.
|
||||
*
|
||||
* @param other The other type, where we check if it is the supertype of {@code 'this'}.
|
||||
* @return If {@code 'this'} is a subtype of {@code 'other'}.
|
||||
*/
|
||||
boolean isSubtypeOf(DataName.Type other);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link FilteredSet} represents a filtered set of {@link DataName}s in the current scope.
|
||||
* It can be obtained with {@link Template#dataNames}. It can be used to count the
|
||||
* available {@link DataName}s, or sample a random {@link DataName} according to the
|
||||
* weights of the {@link DataName}s in the filtered set.
|
||||
* Note: The {@link FilteredSet} is only a filtered view on the set of {@link DataName}s,
|
||||
* and may return different results in different contexts.
|
||||
*/
|
||||
public static final class FilteredSet {
|
||||
private final Mutability mutability;
|
||||
private final DataName.Type subtype;
|
||||
private final DataName.Type supertype;
|
||||
|
||||
FilteredSet(Mutability mutability, DataName.Type subtype, DataName.Type supertype) {
|
||||
this.mutability = mutability;
|
||||
this.subtype = subtype;
|
||||
this.supertype = supertype;
|
||||
}
|
||||
|
||||
FilteredSet(Mutability mutability) {
|
||||
this(mutability, null, null);
|
||||
}
|
||||
|
||||
NameSet.Predicate predicate() {
|
||||
if (subtype == null && supertype == null) {
|
||||
throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'.");
|
||||
}
|
||||
return (Name name) -> {
|
||||
if (!(name instanceof DataName dataName)) { return false; }
|
||||
if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; }
|
||||
if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; }
|
||||
if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; }
|
||||
if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; }
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FilteredSet}, where all {@link DataName}s must be subtypes of {@code type}.
|
||||
*
|
||||
* @param type The type of which all {@link DataName}s must be subtypes of.
|
||||
* @return The updated filtered set.
|
||||
* @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with
|
||||
* {@link #subtypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public FilteredSet subtypeOf(DataName.Type type) {
|
||||
if (subtype != null) {
|
||||
throw new UnsupportedOperationException("Cannot constrain to subtype " + type + ", is already constrained: " + subtype);
|
||||
}
|
||||
return new FilteredSet(mutability, type, supertype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FilteredSet}, where all {@link DataName}s must be supertypes of {@code type}.
|
||||
*
|
||||
* @param type The type of which all {@link DataName}s must be supertype of.
|
||||
* @return The updated filtered set.
|
||||
* @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with
|
||||
* {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public FilteredSet supertypeOf(DataName.Type type) {
|
||||
if (supertype != null) {
|
||||
throw new UnsupportedOperationException("Cannot constrain to supertype " + type + ", is already constrained: " + supertype);
|
||||
}
|
||||
return new FilteredSet(mutability, subtype, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FilteredSet}, where all {@link DataName}s must be of exact {@code type},
|
||||
* hence it must be both subtype and supertype thereof.
|
||||
*
|
||||
* @param type The type of which all {@link DataName}s must be.
|
||||
* @return The updated filtered set.
|
||||
* @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public FilteredSet exactOf(DataName.Type type) {
|
||||
return subtypeOf(type).supertypeOf(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples a random {@link DataName} from the filtered set, according to the weights
|
||||
* of the contained {@link DataName}s.
|
||||
*
|
||||
* @return The sampled {@link DataName}.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
* @throws RendererException If the set was empty.
|
||||
*/
|
||||
public DataName sample() {
|
||||
DataName n = (DataName)Renderer.getCurrent().sampleName(predicate());
|
||||
if (n == null) {
|
||||
String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")";
|
||||
String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")";
|
||||
throw new RendererException("No variable: " + mutability + msg1 + msg2 + ".");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of {@link DataName}s in the filtered set.
|
||||
*
|
||||
* @return The number of {@link DataName}s in the filtered set.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public int count() {
|
||||
return Renderer.getCurrent().countNames(predicate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any {@link DataName}s in the filtered set.
|
||||
*
|
||||
* @return Returns {@code true} iff there is at least one {@link DataName} in the filtered set.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public boolean hasAny() {
|
||||
return Renderer.getCurrent().hasAnyNames(predicate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all {@link DataName}s in the filtered set.
|
||||
*
|
||||
* @return A {@link List} of all {@link DataName}s in the filtered set.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public List<DataName> toList() {
|
||||
List<Name> list = Renderer.getCurrent().listNames(predicate());
|
||||
return list.stream().map(n -> (DataName)n).toList();
|
||||
}
|
||||
}
|
||||
}
|
100
test/hotspot/jtreg/compiler/lib/template_framework/Hook.java
Normal file
100
test/hotspot/jtreg/compiler/lib/template_framework/Hook.java
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
/**
|
||||
* {@link Hook}s can be {@link #anchor}ed for a certain scope in a Template, and all nested
|
||||
* Templates in this scope, and then from within this scope, any Template can
|
||||
* {@link #insert} code to where the {@link Hook} was {@link #anchor}ed. This can be useful to reach
|
||||
* "back" or to some outer scope, e.g. while generating code for a method, one can reach out
|
||||
* to the class scope to insert fields.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
* {@snippet lang=java :
|
||||
* var myHook = new Hook("MyHook");
|
||||
*
|
||||
* var template1 = Template.make("name", (String name) -> body(
|
||||
* """
|
||||
* public static int #name = 42;
|
||||
* """
|
||||
* ));
|
||||
*
|
||||
* var template2 = Template.make(() -> body(
|
||||
* """
|
||||
* public class Test {
|
||||
* """,
|
||||
* // Anchor the hook here.
|
||||
* myHook.anchor(
|
||||
* """
|
||||
* public static void main(String[] args) {
|
||||
* System.out.println("$field: " + $field)
|
||||
* """,
|
||||
* // Reach out to where the hook was anchored, and insert the code of template1.
|
||||
* myHook.insert(template1.asToken($("field"))),
|
||||
* """
|
||||
* }
|
||||
* """
|
||||
* ),
|
||||
* """
|
||||
* }
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param name The name of the Hook, for debugging purposes only.
|
||||
*/
|
||||
public record Hook(String name) {
|
||||
/**
|
||||
* Anchor this {@link Hook} for the scope of the provided {@code 'tokens'}.
|
||||
* From anywhere inside this scope, even in nested Templates, code can be
|
||||
* {@link #insert}ed back to the location where this {@link Hook} was {@link #anchor}ed.
|
||||
*
|
||||
* @param tokens A list of tokens, which have the same restrictions as {@link Template#body}.
|
||||
* @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s.
|
||||
*/
|
||||
public Token anchor(Object... tokens) {
|
||||
return new HookAnchorToken(this, Token.parse(tokens));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a {@link TemplateToken} to the innermost location where this {@link Hook} was {@link #anchor}ed.
|
||||
* This could be in the same Template, or one nested further out.
|
||||
*
|
||||
* @param templateToken The Template with applied arguments to be inserted at the {@link Hook}.
|
||||
* @return The {@link Token} which when used inside a {@link Template#body} performs the code insertion into the {@link Hook}.
|
||||
*/
|
||||
public Token insert(TemplateToken templateToken) {
|
||||
return new HookInsertToken(this, templateToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope.
|
||||
*
|
||||
* @return If the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope.
|
||||
*/
|
||||
public boolean isAnchored() {
|
||||
return Renderer.getCurrent().isAnchored(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
record HookAnchorToken(Hook hook, List<Token> tokens) implements Token {}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
record HookInsertToken(Hook hook, TemplateToken templateToken) implements Token {}
|
50
test/hotspot/jtreg/compiler/lib/template_framework/Name.java
Normal file
50
test/hotspot/jtreg/compiler/lib/template_framework/Name.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
sealed interface Name permits DataName, StructuralName {
|
||||
/**
|
||||
* The name of the name, that can be used in code.
|
||||
*
|
||||
* @return The {@link String} name of the name, that can be used in code.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* The type of the name, allowing for filtering by type.
|
||||
*
|
||||
* @return The type of the name.
|
||||
*/
|
||||
Type type();
|
||||
|
||||
/**
|
||||
* The weight of the name, corresponds to the probability of
|
||||
* choosing this name when sampling.
|
||||
*
|
||||
* @return The weight of the name.
|
||||
*/
|
||||
int weight();
|
||||
|
||||
interface Type {}
|
||||
}
|
151
test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java
Normal file
151
test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Optional;
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
|
||||
/**
|
||||
* The {@link NameSet} defines a set of {@link Name}s (e.g. fields or variable names). They extend the
|
||||
* set of the {@code 'parent'} set.
|
||||
*/
|
||||
class NameSet {
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
|
||||
private final NameSet parent;
|
||||
private final List<NameSet> children = new ArrayList<>();
|
||||
private final List<Name> names = new ArrayList<>();
|
||||
|
||||
interface Predicate {
|
||||
boolean check(Name type);
|
||||
}
|
||||
|
||||
NameSet(NameSet parent) {
|
||||
this.parent = parent;
|
||||
if (parent != null) { parent.registerChild(this); }
|
||||
}
|
||||
|
||||
void registerChild(NameSet child) {
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
private long weight(Predicate predicate) {
|
||||
long w = names.stream().filter(predicate::check).mapToInt(Name::weight).sum();
|
||||
if (parent != null) { w += parent.weight(predicate); }
|
||||
return w;
|
||||
}
|
||||
|
||||
public int count(Predicate predicate) {
|
||||
int c = (int)names.stream().filter(predicate::check).count();
|
||||
if (parent != null) { c += parent.count(predicate); }
|
||||
return c;
|
||||
}
|
||||
|
||||
public boolean hasAny(Predicate predicate) {
|
||||
return names.stream().anyMatch(predicate::check) ||
|
||||
(parent != null && parent.hasAny(predicate));
|
||||
}
|
||||
|
||||
public List<Name> toList(Predicate predicate) {
|
||||
List<Name> list = (parent != null) ? parent.toList(predicate)
|
||||
: new ArrayList<>();
|
||||
list.addAll(names.stream().filter(predicate::check).toList());
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly sample a name from this set or a parent set, restricted to the predicate.
|
||||
*/
|
||||
public Name sample(Predicate predicate) {
|
||||
long w = weight(predicate);
|
||||
if (w <= 0) {
|
||||
// Negative weight should never happen, as all names have positive weight.
|
||||
if (w < 0) {
|
||||
throw new RuntimeException("Negative weight not allowed: " + w);
|
||||
}
|
||||
// If the weight is zero, there is no matching Name available.
|
||||
// Return null, and let the caller handle the situation, e.g.
|
||||
// throw an exception.
|
||||
return null;
|
||||
}
|
||||
|
||||
long r = RANDOM.nextLong(w);
|
||||
return sample(predicate, r);
|
||||
}
|
||||
|
||||
private Name sample(Predicate predicate, long r) {
|
||||
for (var name : names) {
|
||||
if (predicate.check(name)) {
|
||||
r -= name.weight();
|
||||
if (r < 0) { return name; }
|
||||
}
|
||||
}
|
||||
return parent.sample(predicate, r);
|
||||
}
|
||||
|
||||
private Name findLocal(String name) {
|
||||
Optional<Name> opt = names.stream().filter(n -> n.name().equals(name)).findAny();
|
||||
return opt.orElse(null);
|
||||
}
|
||||
|
||||
private Name findParents(String name) {
|
||||
if (parent == null) { return null; }
|
||||
Name n = parent.findLocal(name);
|
||||
if (n != null) { return n; }
|
||||
return parent.findParents(name);
|
||||
}
|
||||
|
||||
private Name findChildren(String name) {
|
||||
for (NameSet child : children) {
|
||||
Name n1 = child.findLocal(name);
|
||||
if (n1 != null) { return n1; }
|
||||
Name n2 = child.findChildren(name);
|
||||
if (n2 != null) { return n2; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Name find(String name) {
|
||||
Name n1 = findLocal(name);
|
||||
if (n1 != null) { return n1; }
|
||||
Name n2 = findParents(name);
|
||||
if (n2 != null) { return n2; }
|
||||
return findChildren(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a variable of a specified type to the set.
|
||||
*/
|
||||
public void add(Name name) {
|
||||
Name other = find(name.name());
|
||||
if (other != null) {
|
||||
throw new RendererException("Duplicate name: " + name + ", previously: " + other);
|
||||
}
|
||||
names.add(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
record NothingToken() implements Token {}
|
12
test/hotspot/jtreg/compiler/lib/template_framework/README.md
Normal file
12
test/hotspot/jtreg/compiler/lib/template_framework/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Template Framework
|
||||
The Template Framework allows the generation of code with Templates. The goal is that these Templates are easy to write, and allow regression tests to cover a larger scope, and to make template based fuzzing easy to extend.
|
||||
|
||||
We want to make it easy to generate variants of tests. Often, we would like to have a set of tests, corresponding to a set of types, a set of operators, a set of constants, etc. Writing all the tests by hand is cumbersome or even impossible. When generating such tests with scripts, it would be preferable if the code generation happens automatically, and the generator script was checked into the code base. Code generation can go beyond simple regression tests, and one might want to generate random code from a list of possible templates, to fuzz individual Java features and compiler optimizations.
|
||||
|
||||
The Template Framework provides a facility to generate code with Templates. Templates are essentially a list of tokens that are concatenated (i.e. rendered) to a String. The Templates can have "holes", which are filled (replaced) by different values at each Template instantiation. For example, these "holes" can be filled with different types, operators or constants. Templates can also be nested, allowing a modular use of Templates.
|
||||
|
||||
Detailed documentation can be found in [Template.java](./Template.java).
|
||||
|
||||
The Template Framework only generates code in the form of a String. This code can then be compiled and executed, for example with the help of the [Compile Framework](../compile_framework/README.md).
|
||||
|
||||
The basic functionalities of the Template Framework are described in the [Template Interface](./Template.java), together with some examples. More examples can be found in [TestSimple.java](../../../testlibrary_tests/template_framework/examples/TestSimple.java), [TestAdvanced.java](../../../testlibrary_tests/template_framework/examples/TestAdvanced.java) and [TestTutorial.java](../../../testlibrary_tests/template_framework/examples/TestTutorial.java).
|
437
test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java
Normal file
437
test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java
Normal file
@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The {@link Renderer} class renders a tokenized {@link Template} in the form of a {@link TemplateToken}.
|
||||
* It also keeps track of the states during a nested Template rendering. There can only be a single
|
||||
* {@link Renderer} active at any point, since there are static methods that reference
|
||||
* {@link Renderer#getCurrent}.
|
||||
*
|
||||
* <p>
|
||||
* The {@link Renderer} instance keeps track of the current frames.
|
||||
*
|
||||
* @see TemplateFrame
|
||||
* @see CodeFrame
|
||||
*/
|
||||
final class Renderer {
|
||||
private static final String NAME_CHARACTERS = "[a-zA-Z_][a-zA-Z0-9_]*";
|
||||
private static final Pattern NAME_PATTERN = Pattern.compile(
|
||||
// We are parsing patterns:
|
||||
// #name
|
||||
// #{name}
|
||||
// $name
|
||||
// ${name}
|
||||
// But the "#" or "$" have already been removed, and the String
|
||||
// starts at the character after that.
|
||||
// The pattern must be at the beginning of the String part.
|
||||
"^" +
|
||||
// We either have "name" or "{name}"
|
||||
"(?:" + // non-capturing group for the OR
|
||||
// capturing group for "name"
|
||||
"(" + NAME_CHARACTERS + ")" +
|
||||
"|" + // OR
|
||||
// We want to trim off the brackets, so have
|
||||
// another non-capturing group.
|
||||
"(?:\\{" +
|
||||
// capturing group for "name" inside "{name}"
|
||||
"(" + NAME_CHARACTERS + ")" +
|
||||
"\\})" +
|
||||
")");
|
||||
private static final Pattern NAME_CHARACTERS_PATTERN = Pattern.compile("^" + NAME_CHARACTERS + "$");
|
||||
|
||||
static boolean isValidHashtagOrDollarName(String name) {
|
||||
return NAME_CHARACTERS_PATTERN.matcher(name).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* There can be at most one Renderer instance at any time.
|
||||
*
|
||||
* <p>
|
||||
* When using nested templates, the user of the Template Framework may be tempted to first render
|
||||
* the nested template to a {@link String}, and then use this {@link String} as a token in an outer
|
||||
* {@link Template#body}. This would be a bad pattern: the outer and nested {@link Template} would
|
||||
* be rendered separately, and could not interact. For example, the nested {@link Template} would
|
||||
* not have access to the scopes of the outer {@link Template}. The inner {@link Template} could
|
||||
* not access {@link Name}s and {@link Hook}s from the outer {@link Template}. The user might assume
|
||||
* that the inner {@link Template} has access to the outer {@link Template}, but they would actually
|
||||
* be separated. This could lead to unexpected behavior or even bugs.
|
||||
*
|
||||
* <p>
|
||||
* Instead, the user should create a {@link TemplateToken} from the inner {@link Template}, and
|
||||
* use that {@link TemplateToken} in the {@link Template#body} of the outer {@link Template}.
|
||||
* This way, the inner and outer {@link Template}s get rendered together, and the inner {@link Template}
|
||||
* has access to the {@link Name}s and {@link Hook}s of the outer {@link Template}.
|
||||
*
|
||||
* <p>
|
||||
* The {@link Renderer} instance exists during the whole rendering process. Should the user ever
|
||||
* attempt to render a nested {@link Template} to a {@link String}, we would detect that there is
|
||||
* already a {@link Renderer} instance for the outer {@link Template}, and throw a {@link RendererException}.
|
||||
*/
|
||||
private static Renderer renderer = null;
|
||||
|
||||
private int nextTemplateFrameId;
|
||||
private final TemplateFrame baseTemplateFrame;
|
||||
private TemplateFrame currentTemplateFrame;
|
||||
private final CodeFrame baseCodeFrame;
|
||||
private CodeFrame currentCodeFrame;
|
||||
|
||||
// We do not want any other instances, so we keep it private.
|
||||
private Renderer(float fuel) {
|
||||
nextTemplateFrameId = 0;
|
||||
baseTemplateFrame = TemplateFrame.makeBase(nextTemplateFrameId++, fuel);
|
||||
currentTemplateFrame = baseTemplateFrame;
|
||||
baseCodeFrame = CodeFrame.makeBase();
|
||||
currentCodeFrame = baseCodeFrame;
|
||||
}
|
||||
|
||||
static Renderer getCurrent() {
|
||||
if (renderer == null) {
|
||||
throw new RendererException("A Template method such as '$', 'let', 'sample', 'count' etc. was called outside a template rendering.");
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
|
||||
static String render(TemplateToken templateToken) {
|
||||
return render(templateToken, Template.DEFAULT_FUEL);
|
||||
}
|
||||
|
||||
static String render(TemplateToken templateToken, float fuel) {
|
||||
// Check nobody else is using the Renderer.
|
||||
if (renderer != null) {
|
||||
throw new RendererException("Nested render not allowed. Please only use 'asToken' inside Templates, and call 'render' only once at the end.");
|
||||
}
|
||||
try {
|
||||
renderer = new Renderer(fuel);
|
||||
renderer.renderTemplateToken(templateToken);
|
||||
renderer.checkFrameConsistencyAfterRendering();
|
||||
return renderer.collectCode();
|
||||
} finally {
|
||||
// Release the Renderer.
|
||||
renderer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFrameConsistencyAfterRendering() {
|
||||
// Ensure CodeFrame consistency.
|
||||
if (baseCodeFrame != currentCodeFrame) {
|
||||
throw new RuntimeException("Internal error: Renderer did not end up at base CodeFrame.");
|
||||
}
|
||||
// Ensure TemplateFrame consistency.
|
||||
if (baseTemplateFrame != currentTemplateFrame) {
|
||||
throw new RuntimeException("Internal error: Renderer did not end up at base TemplateFrame.");
|
||||
}
|
||||
}
|
||||
|
||||
private String collectCode() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
baseCodeFrame.getCode().renderTo(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
String $(String name) {
|
||||
return currentTemplateFrame.$(name);
|
||||
}
|
||||
|
||||
void addHashtagReplacement(String key, Object value) {
|
||||
currentTemplateFrame.addHashtagReplacement(key, format(value));
|
||||
}
|
||||
|
||||
private String getHashtagReplacement(String key) {
|
||||
return currentTemplateFrame.getHashtagReplacement(key);
|
||||
}
|
||||
|
||||
float fuel() {
|
||||
return currentTemplateFrame.fuel;
|
||||
}
|
||||
|
||||
void setFuelCost(float fuelCost) {
|
||||
currentTemplateFrame.setFuelCost(fuelCost);
|
||||
}
|
||||
|
||||
Name sampleName(NameSet.Predicate predicate) {
|
||||
return currentCodeFrame.sampleName(predicate);
|
||||
}
|
||||
|
||||
int countNames(NameSet.Predicate predicate) {
|
||||
return currentCodeFrame.countNames(predicate);
|
||||
}
|
||||
|
||||
boolean hasAnyNames(NameSet.Predicate predicate) {
|
||||
return currentCodeFrame.hasAnyNames(predicate);
|
||||
}
|
||||
|
||||
List<Name> listNames(NameSet.Predicate predicate) {
|
||||
return currentCodeFrame.listNames(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats values to {@link String} with the goal of using them in Java code.
|
||||
* By default, we use the overrides of {@link Object#toString}.
|
||||
* But for some boxed primitives we need to create a special formatting.
|
||||
*/
|
||||
static String format(Object value) {
|
||||
return switch (value) {
|
||||
case String s -> s;
|
||||
case Integer i -> i.toString();
|
||||
// We need to append the "L" so that the values are not interpreted as ints,
|
||||
// and then javac might complain that the values are too large for an int.
|
||||
case Long l -> l.toString() + "L";
|
||||
// Some Float and Double values like Infinity and NaN need a special representation.
|
||||
case Float f -> formatFloat(f);
|
||||
case Double d -> formatDouble(d);
|
||||
default -> value.toString();
|
||||
};
|
||||
}
|
||||
|
||||
private static String formatFloat(Float f) {
|
||||
if (Float.isFinite(f)) {
|
||||
return f.toString() + "f";
|
||||
} else if (f.isNaN()) {
|
||||
return "Float.intBitsToFloat(" + Float.floatToRawIntBits(f) + " /* NaN */)";
|
||||
} else if (f.isInfinite()) {
|
||||
if (f > 0) {
|
||||
return "Float.POSITIVE_INFINITY";
|
||||
} else {
|
||||
return "Float.NEGATIVE_INFINITY";
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Not handled: " + f);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDouble(Double d) {
|
||||
if (Double.isFinite(d)) {
|
||||
return d.toString();
|
||||
} else if (d.isNaN()) {
|
||||
return "Double.longBitsToDouble(" + Double.doubleToRawLongBits(d) + "L /* NaN */)";
|
||||
} else if (d.isInfinite()) {
|
||||
if (d > 0) {
|
||||
return "Double.POSITIVE_INFINITY";
|
||||
} else {
|
||||
return "Double.NEGATIVE_INFINITY";
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Not handled: " + d);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTemplateToken(TemplateToken templateToken) {
|
||||
TemplateFrame templateFrame = TemplateFrame.make(currentTemplateFrame, nextTemplateFrameId++);
|
||||
currentTemplateFrame = templateFrame;
|
||||
|
||||
templateToken.visitArguments((name, value) -> addHashtagReplacement(name, format(value)));
|
||||
TemplateBody body = templateToken.instantiate();
|
||||
renderTokenList(body.tokens());
|
||||
|
||||
if (currentTemplateFrame != templateFrame) {
|
||||
throw new RuntimeException("Internal error: TemplateFrame mismatch!");
|
||||
}
|
||||
currentTemplateFrame = currentTemplateFrame.parent;
|
||||
}
|
||||
|
||||
private void renderToken(Token token) {
|
||||
switch (token) {
|
||||
case StringToken(String s) -> {
|
||||
renderStringWithDollarAndHashtagReplacements(s);
|
||||
}
|
||||
case NothingToken() -> {
|
||||
// Nothing.
|
||||
}
|
||||
case HookAnchorToken(Hook hook, List<Token> tokens) -> {
|
||||
CodeFrame outerCodeFrame = currentCodeFrame;
|
||||
|
||||
// We need a CodeFrame to which the hook can insert code. That way, name
|
||||
// definitions at the hook cannot escape the hookCodeFrame.
|
||||
CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame);
|
||||
hookCodeFrame.addHook(hook);
|
||||
|
||||
// We need a CodeFrame where the tokens can be rendered. That way, name
|
||||
// definitions from the tokens cannot escape the innerCodeFrame to the
|
||||
// hookCodeFrame.
|
||||
CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame);
|
||||
currentCodeFrame = innerCodeFrame;
|
||||
|
||||
renderTokenList(tokens);
|
||||
|
||||
// Close the hookCodeFrame and innerCodeFrame. hookCodeFrame code comes before the
|
||||
// innerCodeFrame code from the tokens.
|
||||
currentCodeFrame = outerCodeFrame;
|
||||
currentCodeFrame.addCode(hookCodeFrame.getCode());
|
||||
currentCodeFrame.addCode(innerCodeFrame.getCode());
|
||||
}
|
||||
case HookInsertToken(Hook hook, TemplateToken templateToken) -> {
|
||||
// Switch to hook CodeFrame.
|
||||
CodeFrame callerCodeFrame = currentCodeFrame;
|
||||
CodeFrame hookCodeFrame = codeFrameForHook(hook);
|
||||
|
||||
// Use a transparent nested CodeFrame. We need a CodeFrame so that the code generated
|
||||
// by the TemplateToken can be collected, and hook insertions from it can still
|
||||
// be made to the hookCodeFrame before the code from the TemplateToken is added to
|
||||
// the hookCodeFrame.
|
||||
// But the CodeFrame must be transparent, so that its name definitions go out to
|
||||
// the hookCodeFrame, and are not limited to the CodeFrame for the TemplateToken.
|
||||
currentCodeFrame = CodeFrame.makeTransparentForNames(hookCodeFrame);
|
||||
|
||||
renderTemplateToken(templateToken);
|
||||
|
||||
hookCodeFrame.addCode(currentCodeFrame.getCode());
|
||||
|
||||
// Switch back from hook CodeFrame to caller CodeFrame.
|
||||
currentCodeFrame = callerCodeFrame;
|
||||
}
|
||||
case TemplateToken templateToken -> {
|
||||
// Use a nested CodeFrame.
|
||||
CodeFrame callerCodeFrame = currentCodeFrame;
|
||||
currentCodeFrame = CodeFrame.make(currentCodeFrame);
|
||||
|
||||
renderTemplateToken(templateToken);
|
||||
|
||||
callerCodeFrame.addCode(currentCodeFrame.getCode());
|
||||
currentCodeFrame = callerCodeFrame;
|
||||
}
|
||||
case AddNameToken(Name name) -> {
|
||||
currentCodeFrame.addName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderTokenList(List<Token> tokens) {
|
||||
CodeFrame codeFrame = currentCodeFrame;
|
||||
for (Token t : tokens) {
|
||||
renderToken(t);
|
||||
}
|
||||
if (codeFrame != currentCodeFrame) {
|
||||
throw new RuntimeException("Internal error: CodeFrame mismatch.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We split a {@link String} by "#" and "$", and then look at each part.
|
||||
* Example:
|
||||
*
|
||||
* s: "abcdefghijklmnop #name abcdefgh${var_name} 12345#{name2}_con $field_name something"
|
||||
* parts: --------0-------- ------1------ --------2------- ------3----- ----------4---------
|
||||
* start: ^ ^ ^ ^ ^
|
||||
* next: ^ ^ ^ ^ ^
|
||||
* none hashtag dollar hashtag dollar done
|
||||
*/
|
||||
private void renderStringWithDollarAndHashtagReplacements(final String s) {
|
||||
int count = 0; // First part needs special handling
|
||||
int start = 0;
|
||||
boolean startIsAfterDollar = false;
|
||||
do {
|
||||
// Find the next "$" or "#", after start.
|
||||
int dollar = s.indexOf("$", start);
|
||||
int hashtag = s.indexOf("#", start);
|
||||
// If the character was not found, we want to have the rest of the
|
||||
// String s, so instead of "-1" take the end/length of the String.
|
||||
dollar = (dollar == -1) ? s.length() : dollar;
|
||||
hashtag = (hashtag == -1) ? s.length() : hashtag;
|
||||
// Take the first one.
|
||||
int next = Math.min(dollar, hashtag);
|
||||
String part = s.substring(start, next);
|
||||
|
||||
if (count == 0) {
|
||||
// First part has no "#" or "$" before it.
|
||||
currentCodeFrame.addString(part);
|
||||
} else {
|
||||
// All others must do the replacement.
|
||||
renderStringWithDollarAndHashtagReplacementsPart(s, part, startIsAfterDollar);
|
||||
}
|
||||
|
||||
if (next == s.length()) {
|
||||
// No new "#" or "$" was found, we just processed the rest of the String,
|
||||
// terminate now.
|
||||
return;
|
||||
}
|
||||
start = next + 1; // skip over the "#" or "$"
|
||||
startIsAfterDollar = next == dollar; // remember which character we just split with
|
||||
count++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are parsing a part now. Before the part, there was either a "#" or "$":
|
||||
* isDollar = false:
|
||||
* "#part"
|
||||
* "#name abcdefgh"
|
||||
* ----
|
||||
* "#{name2}_con "
|
||||
* -------
|
||||
*
|
||||
* isDollar = true:
|
||||
* "$part"
|
||||
* "${var_name} 12345"
|
||||
* ----------
|
||||
* "$field_name something"
|
||||
* ----------
|
||||
*
|
||||
* We now want to find the name pattern at the beginning of the part, and replace
|
||||
* it according to the hashtag or dollar replacement strategy.
|
||||
*/
|
||||
private void renderStringWithDollarAndHashtagReplacementsPart(final String s, final String part, final boolean isDollar) {
|
||||
Matcher matcher = NAME_PATTERN.matcher(part);
|
||||
// If the string has a "#" or "$" that is not followed by a correct name
|
||||
// pattern, then the matcher will not match. These can be cases like:
|
||||
// "##name" -> the first hashtag leads to an empty part, and an empty name.
|
||||
// "#1name" -> the name pattern does not allow a digit as the first character.
|
||||
// "anything#" -> a hashtag at the end of the string leads to an empty name.
|
||||
if (!matcher.find()) {
|
||||
String replacement = isDollar ? "$" : "#";
|
||||
throw new RendererException("Is not a valid '" + replacement + "' replacement pattern: '" +
|
||||
replacement + part + "' in '" + s + "'.");
|
||||
}
|
||||
// We know that there is a correct pattern, and now we replace it.
|
||||
currentCodeFrame.addString(matcher.replaceFirst(
|
||||
(MatchResult result) -> {
|
||||
// There are two groups: (1) for "name" and (2) for "{name}"
|
||||
String name = result.group(1) != null ? result.group(1) : result.group(2);
|
||||
if (isDollar) {
|
||||
return $(name);
|
||||
} else {
|
||||
// replaceFirst needs some special escaping of backslashes and ollar signs.
|
||||
return getHashtagReplacement(name).replace("\\", "\\\\").replace("$", "\\$");
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
boolean isAnchored(Hook hook) {
|
||||
return currentCodeFrame.codeFrameForHook(hook) != null;
|
||||
}
|
||||
|
||||
private CodeFrame codeFrameForHook(Hook hook) {
|
||||
CodeFrame codeFrame = currentCodeFrame.codeFrameForHook(hook);
|
||||
if (codeFrame == null) {
|
||||
throw new RendererException("Hook '" + hook.name() + "' was referenced but not found!");
|
||||
}
|
||||
return codeFrame;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
/**
|
||||
* This exception is thrown when something goes wrong during Template
|
||||
* rendering, or in the use of any of its static methods.
|
||||
* It most likely indicates a wrong use of the Templates.
|
||||
*/
|
||||
public class RendererException extends RuntimeException {
|
||||
RendererException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
record StringToken(String value) implements Token {}
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link StructuralName}s represent things like method and class names, and can be added to the local
|
||||
* scope with {@link Template#addStructuralName}, and accessed with {@link Template#structuralNames}, from where
|
||||
* count, list or even sample random {@link StructuralName}s. Every {@link StructuralName} has a {@link StructuralName.Type},
|
||||
* so that sampling can be restricted to these types.
|
||||
*
|
||||
* <p>
|
||||
* For field and variable names and alike, there are the analogous {@link DataName}s.
|
||||
*
|
||||
* @param name The {@link String} name used in code.
|
||||
* @param type The type of the {@link StructuralName}.
|
||||
* @param weight The weight of the {@link StructuralName}, it corresponds to the probability of choosing this
|
||||
* {@link StructuralName} when sampling later on.
|
||||
*/
|
||||
public record StructuralName(String name, StructuralName.Type type, int weight) implements Name {
|
||||
|
||||
/**
|
||||
* Creates a new {@link StructuralName}.
|
||||
*/
|
||||
public StructuralName {
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface for the type of a {@link StructuralName}.
|
||||
*/
|
||||
public interface Type extends Name.Type {
|
||||
/**
|
||||
* The name of the type, that can be used in code.
|
||||
*
|
||||
* @return The {@link String} representation of the type, that can be used in code.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Defines the subtype relationship with other types, which is used to filter {@link StructuralName}s
|
||||
* in {@link FilteredSet#exactOf}, {@link FilteredSet#subtypeOf}, and {@link FilteredSet#supertypeOf}.
|
||||
*
|
||||
* @param other The other type, where we check if it is the supertype of {@code 'this'}.
|
||||
* @return If {@code 'this'} is a subtype of {@code 'other'}.
|
||||
*/
|
||||
boolean isSubtypeOf(StructuralName.Type other);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link FilteredSet} represents a filtered set of {@link StructuralName}s in the current scope.
|
||||
* It can be obtained with {@link Template#structuralNames}. It can be used to count the
|
||||
* available {@link StructuralName}s, or sample a random {@link StructuralName} according to the
|
||||
* weights of the {@link StructuralName}s in the filtered set.
|
||||
* Note: The {@link FilteredSet} is only a filtered view on the set of {@link StructuralName}s,
|
||||
* and may return different results in different contexts.
|
||||
*/
|
||||
public static final class FilteredSet {
|
||||
private final StructuralName.Type subtype;
|
||||
private final StructuralName.Type supertype;
|
||||
|
||||
FilteredSet(StructuralName.Type subtype, StructuralName.Type supertype) {
|
||||
this.subtype = subtype;
|
||||
this.supertype = supertype;
|
||||
}
|
||||
|
||||
FilteredSet() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
NameSet.Predicate predicate() {
|
||||
if (subtype == null && supertype == null) {
|
||||
throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'.");
|
||||
}
|
||||
return (Name name) -> {
|
||||
if (!(name instanceof StructuralName structuralName)) { return false; }
|
||||
if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; }
|
||||
if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; }
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FilteredSet}, where all {@link StructuralName}s must be subtypes of {@code type}.
|
||||
*
|
||||
* @param type The type of which all {@link StructuralName}s must be subtypes of.
|
||||
* @return The updated filtered set.
|
||||
* @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with
|
||||
* {@link #subtypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public FilteredSet subtypeOf(StructuralName.Type type) {
|
||||
if (subtype != null) {
|
||||
throw new UnsupportedOperationException("Cannot constrain to subtype " + type + ", is already constrained: " + subtype);
|
||||
}
|
||||
return new FilteredSet(type, supertype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FilteredSet}, where all {@link StructuralName}s must be supertypes of {@code type}.
|
||||
*
|
||||
* @param type The type of which all {@link StructuralName}s must be supertype of.
|
||||
* @return The updated filtered set.
|
||||
* @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with
|
||||
* {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public FilteredSet supertypeOf(StructuralName.Type type) {
|
||||
if (supertype != null) {
|
||||
throw new UnsupportedOperationException("Cannot constrain to supertype " + type + ", is already constrained: " + supertype);
|
||||
}
|
||||
return new FilteredSet(subtype, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FilteredSet}, where all {@link StructuralName}s must be of exact {@code type},
|
||||
* hence it must be both subtype and supertype thereof.
|
||||
*
|
||||
* @param type The type of which all {@link StructuralName}s must be.
|
||||
* @return The updated filtered set.
|
||||
* @throws UnsupportedOperationException If this {@link FilteredSet} was already filtered with
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public FilteredSet exactOf(StructuralName.Type type) {
|
||||
return subtypeOf(type).supertypeOf(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples a random {@link StructuralName} from the filtered set, according to the weights
|
||||
* of the contained {@link StructuralName}s.
|
||||
*
|
||||
* @return The sampled {@link StructuralName}.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
* @throws RendererException If the set was empty.
|
||||
*/
|
||||
public StructuralName sample() {
|
||||
StructuralName n = (StructuralName)Renderer.getCurrent().sampleName(predicate());
|
||||
if (n == null) {
|
||||
String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")";
|
||||
String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")";
|
||||
throw new RendererException("No variable:" + msg1 + msg2 + ".");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of {@link StructuralName}s in the filtered set.
|
||||
*
|
||||
* @return The number of {@link StructuralName}s in the filtered set.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public int count() {
|
||||
return Renderer.getCurrent().countNames(predicate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any {@link StructuralName}s in the filtered set.
|
||||
*
|
||||
* @return Returns {@code true} iff there is at least one {@link StructuralName} in the filtered set.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public boolean hasAny() {
|
||||
return Renderer.getCurrent().hasAnyNames(predicate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all {@link StructuralName}s in the filtered set.
|
||||
*
|
||||
* @return A {@link List} of all {@link StructuralName}s in the filtered set.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public List<StructuralName> toList() {
|
||||
List<Name> list = Renderer.getCurrent().listNames(predicate());
|
||||
return list.stream().map(n -> (StructuralName)n).toList();
|
||||
}
|
||||
}
|
||||
}
|
844
test/hotspot/jtreg/compiler/lib/template_framework/Template.java
Normal file
844
test/hotspot/jtreg/compiler/lib/template_framework/Template.java
Normal file
@ -0,0 +1,844 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import compiler.lib.compile_framework.CompileFramework;
|
||||
import compiler.lib.ir_framework.TestFramework;
|
||||
|
||||
/**
|
||||
* The Template Framework allows the generation of code with Templates. The goal is that these Templates are
|
||||
* easy to write, and allow regression tests to cover a larger scope, and to make template based fuzzing easy
|
||||
* to extend.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Motivation:</strong> We want to make it easy to generate variants of tests. Often, we would like to
|
||||
* have a set of tests, corresponding to a set of types, a set of operators, a set of constants, etc. Writing all
|
||||
* the tests by hand is cumbersome or even impossible. When generating such tests with scripts, it would be
|
||||
* preferable if the code generation happens automatically, and the generator script was checked into the code
|
||||
* base. Code generation can go beyond simple regression tests, and one might want to generate random code from
|
||||
* a list of possible templates, to fuzz individual Java features and compiler optimizations.
|
||||
*
|
||||
* <p>
|
||||
* The Template Framework provides a facility to generate code with Templates. A Template is essentially a list
|
||||
* of tokens that are concatenated (i.e. rendered) to a {@link String}. The Templates can have "holes", which are
|
||||
* filled (replaced) by different values at each Template instantiation. For example, these "holes" can
|
||||
* be filled with different types, operators or constants. Templates can also be nested, allowing a modular
|
||||
* use of Templates.
|
||||
*
|
||||
* <p>
|
||||
* Once we rendered the source code to a {@link String}, we can compile it with the {@link CompileFramework}.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Example:</strong>
|
||||
* The following snippets are from the example test {@code TestAdvanced.java}.
|
||||
* First, we define a template that generates a {@code @Test} method for a given type, operator and
|
||||
* constant generator. We define two constants {@code con1} and {@code con2}, and then use a multiline
|
||||
* string with hashtags {@code #} (i.e. "holes") that are then replaced by the template arguments and the
|
||||
* {@link #let} definitions.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body(
|
||||
* let("con1", generator.next()),
|
||||
* let("con2", generator.next()),
|
||||
* """
|
||||
* // #typeName #operator #con1 #con2
|
||||
* public static #typeName $GOLD = $test();
|
||||
*
|
||||
* @Test
|
||||
* public static #typeName $test() {
|
||||
* return (#typeName)(#con1 #operator #con2);
|
||||
* }
|
||||
*
|
||||
* @Check(test = "$test")
|
||||
* public static void $check(#typeName result) {
|
||||
* Verify.checkEQ(result, $GOLD);
|
||||
* }
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* To get an executable test, we define a {@link Template} that produces a class body with a main method. The Template
|
||||
* takes a list of types, and calls the {@code testTemplate} defined above for each type and operator. We use
|
||||
* the {@link TestFramework} to call our {@code @Test} methods.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var classTemplate = Template.make("types", (List<Type> types) -> body(
|
||||
* let("classpath", comp.getEscapedClassPathOfCompiledClasses()),
|
||||
* """
|
||||
* package p.xyz;
|
||||
*
|
||||
* import compiler.lib.ir_framework.*;
|
||||
* import compiler.lib.verify.*;
|
||||
*
|
||||
* public class InnerTest {
|
||||
* public static void main() {
|
||||
* // Set the classpath, so that the TestFramework test VM knows where
|
||||
* // the CompileFramework put the class files of the compiled source code.
|
||||
* TestFramework framework = new TestFramework(InnerTest.class);
|
||||
* framework.addFlags("-classpath", "#classpath");
|
||||
* framework.start();
|
||||
* }
|
||||
*
|
||||
* """,
|
||||
* // Call the testTemplate for each type and operator, generating a
|
||||
* // list of lists of TemplateToken:
|
||||
* types.stream().map((Type type) ->
|
||||
* type.operators().stream().map((String operator) ->
|
||||
* testTemplate.asToken(type.name(), operator, type.generator())).toList()
|
||||
* ).toList(),
|
||||
* """
|
||||
* }
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* Finally, we generate the list of types, and pass it to the class template:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* List<Type> types = List.of(
|
||||
* new Type("byte", GEN_BYTE::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
* new Type("char", GEN_CHAR::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
* new Type("short", GEN_SHORT::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
* new Type("int", GEN_INT::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
* new Type("long", GEN_LONG::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
* new Type("float", GEN_FLOAT::next, List.of("+", "-", "*", "/")),
|
||||
* new Type("double", GEN_DOUBLE::next, List.of("+", "-", "*", "/"))
|
||||
* );
|
||||
*
|
||||
* // Use the template with one argument, and render it to a String.
|
||||
* return classTemplate.render(types);
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* <strong>Details:</strong>
|
||||
* <p>
|
||||
* A {@link Template} can have zero or more arguments. A template can be created with {@code make} methods like
|
||||
* {@link Template#make(String, Function)}. For each number of arguments there is an implementation
|
||||
* (e.g. {@link Template.TwoArgs} for two arguments). This allows the use of generics for the
|
||||
* {@link Template} argument types which enables type checking of the {@link Template} arguments.
|
||||
* It is currently only allowed to use up to three arguments.
|
||||
*
|
||||
* <p>
|
||||
* A {@link Template} can be rendered to a {@link String} (e.g. {@link Template.ZeroArgs#render()}).
|
||||
* Alternatively, we can generate a {@link Token} (more specifically, a {@link TemplateToken}) with {@code asToken()}
|
||||
* (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#body}.
|
||||
*
|
||||
* <p>
|
||||
* Ideally, we would have used <a href="https://openjdk.org/jeps/430">string templates</a> to inject these Template
|
||||
* arguments into the strings. But since string templates are not (yet) available, the Templates provide
|
||||
* <strong>hashtag replacements</strong> in the {@link String}s: the Template argument names are captured, and
|
||||
* the argument values automatically replace any {@code "#name"} in the {@link String}s. See the different overloads
|
||||
* of {@link #make} for examples. Additional hashtag replacements can be defined with {@link #let}.
|
||||
*
|
||||
* <p>
|
||||
* When using nested Templates, there can be collisions with identifiers (e.g. variable names and method names).
|
||||
* For this, Templates provide <strong>dollar replacements</strong>, which automatically rename any
|
||||
* {@code "$name"} in the {@link String} with a {@code "name_ID"}, where the {@code "ID"} is unique for every use of
|
||||
* a Template. The dollar replacement can also be captured with {@link #$}, and passed to nested
|
||||
* Templates, which allows sharing of these identifier names between Templates.
|
||||
*
|
||||
* <p>
|
||||
* The dollar and hashtag names must have at least one character. The first character must be a letter
|
||||
* or underscore (i.e. {@code a-zA-Z_}), the other characters can also be digits (i.e. {@code a-zA-Z0-9_}).
|
||||
* One can use them with or without curly braces, e.g. {@code #name}, {@code #{name}}, {@code $name}, or
|
||||
* {@code #{name}}.
|
||||
*
|
||||
* <p>
|
||||
* A {@link TemplateToken} cannot just be used in {@link Template#body}, but it can also be
|
||||
* {@link Hook#insert}ed to where a {@link Hook} was {@link Hook#anchor}ed earlier (in some outer scope of the code).
|
||||
* For example, while generating code in a method, one can reach out to the scope of the class, and insert a
|
||||
* new field, or define a utility method.
|
||||
*
|
||||
* <p>
|
||||
* A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding,
|
||||
* a Template can reference itself.
|
||||
*
|
||||
* <p>
|
||||
* The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the
|
||||
* approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting
|
||||
* with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each
|
||||
* Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a
|
||||
* template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default
|
||||
* fuel cost with {@link #setFuelCost}) when defining the {@link #body(Object...)}. Recursive templates are
|
||||
* supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero).
|
||||
*
|
||||
* <p>
|
||||
* Code generation can involve keeping track of fields and variables, as well as the scopes in which they
|
||||
* are available, and if they are mutable or immutable. We model fields and variables with {@link DataName}s,
|
||||
* which we can add to the current scope with {@link #addDataName}. We can access the {@link DataName}s with
|
||||
* {@link #dataNames}. We can filter for {@link DataName}s of specific {@link DataName.Type}s, and then
|
||||
* we can call {@link DataName.FilteredSet#count}, {@link DataName.FilteredSet#sample},
|
||||
* {@link DataName.FilteredSet#toList}, etc. There are many use-cases for this mechanism, especially
|
||||
* facilitating communication between the code of outer and inner {@link Template}s. Especially for fuzzing,
|
||||
* it may be useful to be able to add fields and variables, and sample them randomly, to create a random data
|
||||
* flow graph.
|
||||
*
|
||||
* <p>
|
||||
* Similarly, we may want to model method and class names, and possibly other structural names. We model
|
||||
* these names with {@link StructuralName}, which works analogously to {@link DataName}, except that they
|
||||
* are not concerned about mutability.
|
||||
*
|
||||
* <p>
|
||||
* When working with {@link DataName}s and {@link StructuralName}s, it is important to be aware of the
|
||||
* relevant scopes, as well as the execution order of the {@link Template} lambdas and the evaluation
|
||||
* of the {@link Template#body} tokens. When a {@link Template} is rendered, its lambda is invoked. In the
|
||||
* lambda, we generate the tokens, and create the {@link Template#body}. Once the lambda returns, the
|
||||
* tokens are evaluated one by one. While evaluating the tokens, the {@link Renderer} might encounter a nested
|
||||
* {@link TemplateToken}, which in turn triggers the evaluation of that nested {@link Template}, i.e.
|
||||
* the evaluation of its lambda and later the evaluation of its tokens. It is important to keep in mind
|
||||
* that the lambda is always executed first, and the tokens are evaluated afterwards. A method like
|
||||
* {@code dataNames(MUTABLE).exactOf(type).count()} is a method that is executed during the evaluation
|
||||
* of the lambda. But a method like {@link #addDataName} returns a token, and does not immediately add
|
||||
* the {@link DataName}. This ensures that the {@link DataName} is only inserted when the tokens are
|
||||
* evaluated, so that it is inserted at the exact scope where we would expect it.
|
||||
*
|
||||
* <p>
|
||||
* Let us look at the following example to better understand the execution order.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var testTemplate = Template.make(() -> body(
|
||||
* // The lambda has just been invoked.
|
||||
* // We count the DataNames and assign the count to the hashtag replacement "c1".
|
||||
* let("c1", dataNames(MUTABLE).exactOf(someType).count()),
|
||||
* // We want to define a DataName "v1", and create a token for it.
|
||||
* addDataName($("v1"), someType, MUTABLE),
|
||||
* // We count the DataNames again, but the count does NOT change compared to "c1".
|
||||
* // This is because the token for "v1" is only evaluated later.
|
||||
* let("c2", dataNames(MUTABLE).exactOf(someType).count()),
|
||||
* // Create a nested scope.
|
||||
* METHOD_HOOK.anchor(
|
||||
* // We want to define a DataName "v2", which is only valid inside this
|
||||
* // nested scope.
|
||||
* addDataName($("v2"), someType, MUTABLE),
|
||||
* // The count is still not different to "c1".
|
||||
* let("c3", dataNames(MUTABLE).exactOf(someType).count()),
|
||||
* // We nest a Template. This creates a TemplateToken, which is later evaluated.
|
||||
* // By the time the TemplateToken is evaluated, the tokens from above will
|
||||
* // be already evaluated. Hence, "v1" and "v2" are added by then, and if the
|
||||
* // "otherTemplate" were to count the DataNames, the count would be increased
|
||||
* // by 2 compared to "c1".
|
||||
* otherTemplate.asToken()
|
||||
* ),
|
||||
* // After closing the scope, "v2" is no longer available.
|
||||
* // The count is still the same as "c1", since "v1" is still only a token.
|
||||
* let("c4", dataNames(MUTABLE).exactOf(someType).count()),
|
||||
* // We nest another Template. Again, this creates a TemplateToken, which is only
|
||||
* // evaluated later. By that time, the token for "v1" is evaluated, and so the
|
||||
* // nested Template would observe an increment in the count.
|
||||
* anotherTemplate.asToken()
|
||||
* // By this point, all methods are called, and the tokens generated.
|
||||
* // The lambda returns the "body", which is all of the tokens that we just
|
||||
* // generated. After returning from the lambda, the tokens will be evaluated
|
||||
* // one by one.
|
||||
* ));
|
||||
* }
|
||||
|
||||
* <p>
|
||||
* More examples for these functionalities can be found in {@code TestTutorial.java}, {@code TestSimple.java},
|
||||
* and {@code TestAdvanced.java}, which all produce compilable Java code. Additional examples can be found in
|
||||
* the tests, such as {@code TestTemplate.java} and {@code TestFormat.java}, which do not necessarily generate
|
||||
* valid Java code, but generate deterministic Strings which are easier to verify, and may also serve as a
|
||||
* reference when learning about these functionalities.
|
||||
*/
|
||||
public sealed interface Template permits Template.ZeroArgs,
|
||||
Template.OneArg,
|
||||
Template.TwoArgs,
|
||||
Template.ThreeArgs {
|
||||
|
||||
/**
|
||||
* A {@link Template} with no arguments.
|
||||
*
|
||||
* @param function The {@link Supplier} that creates the {@link TemplateBody}.
|
||||
*/
|
||||
record ZeroArgs(Supplier<TemplateBody> function) implements Template {
|
||||
TemplateBody instantiate() {
|
||||
return function.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TemplateToken} which can be used as a {@link Token} inside
|
||||
* a {@link Template} for nested code generation.
|
||||
*
|
||||
* @return The {@link TemplateToken} to use the {@link Template} inside another
|
||||
* {@link Template}.
|
||||
*/
|
||||
public TemplateToken asToken() {
|
||||
return new TemplateToken.ZeroArgs(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render() {
|
||||
return new TemplateToken.ZeroArgs(this).render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param fuel The amount of fuel provided for recursive Template instantiations.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(float fuel) {
|
||||
return new TemplateToken.ZeroArgs(this).render(fuel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Template} with one argument.
|
||||
*
|
||||
* @param arg1Name The name of the (first) argument, used for hashtag replacements in the {@link Template}.
|
||||
* @param <T1> The type of the (first) argument.
|
||||
* @param function The {@link Function} that creates the {@link TemplateBody} given the template argument.
|
||||
*/
|
||||
record OneArg<T1>(String arg1Name, Function<T1, TemplateBody> function) implements Template {
|
||||
TemplateBody instantiate(T1 arg1) {
|
||||
return function.apply(arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TemplateToken} which can be used as a {@link Token} inside
|
||||
* a {@link Template} for nested code generation.
|
||||
*
|
||||
* @param arg1 The value for the (first) argument.
|
||||
* @return The {@link TemplateToken} to use the {@link Template} inside another
|
||||
* {@link Template}.
|
||||
*/
|
||||
public TemplateToken asToken(T1 arg1) {
|
||||
return new TemplateToken.OneArg<>(this, arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(T1 arg1) {
|
||||
return new TemplateToken.OneArg<>(this, arg1).render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param fuel The amount of fuel provided for recursive Template instantiations.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(float fuel, T1 arg1) {
|
||||
return new TemplateToken.OneArg<>(this, arg1).render(fuel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Template} with two arguments.
|
||||
*
|
||||
* @param arg1Name The name of the first argument, used for hashtag replacements in the {@link Template}.
|
||||
* @param arg2Name The name of the second argument, used for hashtag replacements in the {@link Template}.
|
||||
* @param <T1> The type of the first argument.
|
||||
* @param <T2> The type of the second argument.
|
||||
* @param function The {@link BiFunction} that creates the {@link TemplateBody} given the template arguments.
|
||||
*/
|
||||
record TwoArgs<T1, T2>(String arg1Name, String arg2Name, BiFunction<T1, T2, TemplateBody> function) implements Template {
|
||||
TemplateBody instantiate(T1 arg1, T2 arg2) {
|
||||
return function.apply(arg1, arg2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TemplateToken} which can be used as a {@link Token} inside
|
||||
* a {@link Template} for nested code generation.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param arg2 The value for the second argument.
|
||||
* @return The {@link TemplateToken} to use the {@link Template} inside another
|
||||
* {@link Template}.
|
||||
*/
|
||||
public TemplateToken asToken(T1 arg1, T2 arg2) {
|
||||
return new TemplateToken.TwoArgs<>(this, arg1, arg2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param arg2 The value for the second argument.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(T1 arg1, T2 arg2) {
|
||||
return new TemplateToken.TwoArgs<>(this, arg1, arg2).render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param arg2 The value for the second argument.
|
||||
* @param fuel The amount of fuel provided for recursive Template instantiations.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(float fuel, T1 arg1, T2 arg2) {
|
||||
return new TemplateToken.TwoArgs<>(this, arg1, arg2).render(fuel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for function with three arguments.
|
||||
*
|
||||
* @param <T> Type of the first argument.
|
||||
* @param <U> Type of the second argument.
|
||||
* @param <V> Type of the third argument.
|
||||
* @param <R> Type of the return value.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface TriFunction<T, U, V, R> {
|
||||
|
||||
/**
|
||||
* Function definition for the three argument functions.
|
||||
*
|
||||
* @param t The first argument.
|
||||
* @param u The second argument.
|
||||
* @param v The third argument.
|
||||
* @return Return value of the three argument function.
|
||||
*/
|
||||
R apply(T t, U u, V v);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Template} with three arguments.
|
||||
*
|
||||
* @param arg1Name The name of the first argument, used for hashtag replacements in the {@link Template}.
|
||||
* @param arg2Name The name of the second argument, used for hashtag replacements in the {@link Template}.
|
||||
* @param arg3Name The name of the third argument, used for hashtag replacements in the {@link Template}.
|
||||
* @param <T1> The type of the first argument.
|
||||
* @param <T2> The type of the second argument.
|
||||
* @param <T3> The type of the third argument.
|
||||
* @param function The function with three arguments that creates the {@link TemplateBody} given the template arguments.
|
||||
*/
|
||||
record ThreeArgs<T1, T2, T3>(String arg1Name, String arg2Name, String arg3Name, TriFunction<T1, T2, T3, TemplateBody> function) implements Template {
|
||||
TemplateBody instantiate(T1 arg1, T2 arg2, T3 arg3) {
|
||||
return function.apply(arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TemplateToken} which can be used as a {@link Token} inside
|
||||
* a {@link Template} for nested code generation.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param arg2 The value for the second argument.
|
||||
* @param arg3 The value for the third argument.
|
||||
* @return The {@link TemplateToken} to use the {@link Template} inside another
|
||||
* {@link Template}.
|
||||
*/
|
||||
public TemplateToken asToken(T1 arg1, T2 arg2, T3 arg3) {
|
||||
return new TemplateToken.ThreeArgs<>(this, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param arg2 The value for the second argument.
|
||||
* @param arg3 The value for the third argument.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(T1 arg1, T2 arg2, T3 arg3) {
|
||||
return new TemplateToken.ThreeArgs<>(this, arg1, arg2, arg3).render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the {@link Template} to a {@link String}.
|
||||
*
|
||||
* @param arg1 The value for the first argument.
|
||||
* @param arg2 The value for the second argument.
|
||||
* @param arg3 The value for the third argument.
|
||||
* @param fuel The amount of fuel provided for recursive Template instantiations.
|
||||
* @return The {@link String}, resulting from rendering the {@link Template}.
|
||||
*/
|
||||
public String render(float fuel, T1 arg1, T2 arg2, T3 arg3) {
|
||||
return new TemplateToken.ThreeArgs<>(this, arg1, arg2, arg3).render(fuel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with no arguments.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> body(
|
||||
* """
|
||||
* Multi-line string or other tokens.
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @return A {@link Template} with zero arguments.
|
||||
*/
|
||||
static Template.ZeroArgs make(Supplier<TemplateBody> body) {
|
||||
return new Template.ZeroArgs(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with one argument.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* Good practice but not enforced but not enforced: {@code arg1Name} should match the lambda argument name.
|
||||
*
|
||||
* <p>
|
||||
* Here is an example with template argument {@code 'a'}, captured once as string name
|
||||
* for use in hashtag replacements, and captured once as lambda argument with the corresponding type
|
||||
* of the generic argument.
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make("a", (Integer a) -> body(
|
||||
* """
|
||||
* Multi-line string or other tokens.
|
||||
* We can use the hashtag replacement #a to directly insert the String value of a.
|
||||
* """,
|
||||
* "We can also use the captured parameter of a: " + a
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @param <T1> Type of the (first) argument.
|
||||
* @param arg1Name The name of the (first) argument for hashtag replacement.
|
||||
* @return A {@link Template} with one argument.
|
||||
*/
|
||||
static <T1> Template.OneArg<T1> make(String arg1Name, Function<T1, TemplateBody> body) {
|
||||
return new Template.OneArg<>(arg1Name, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with two arguments.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* Good practice but not enforced: {@code arg1Name} and {@code arg2Name} should match the lambda argument names.
|
||||
*
|
||||
* <p>
|
||||
* Here is an example with template arguments {@code 'a'} and {@code 'b'}, captured once as string names
|
||||
* for use in hashtag replacements, and captured once as lambda arguments with the corresponding types
|
||||
* of the generic arguments.
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make("a", "b", (Integer a, String b) -> body(
|
||||
* """
|
||||
* Multi-line string or other tokens.
|
||||
* We can use the hashtag replacement #a and #b to directly insert the String value of a and b.
|
||||
* """,
|
||||
* "We can also use the captured parameter of a and b: " + a + " and " + b
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @param <T1> Type of the first argument.
|
||||
* @param arg1Name The name of the first argument for hashtag replacement.
|
||||
* @param <T2> Type of the second argument.
|
||||
* @param arg2Name The name of the second argument for hashtag replacement.
|
||||
* @return A {@link Template} with two arguments.
|
||||
*/
|
||||
static <T1, T2> Template.TwoArgs<T1, T2> make(String arg1Name, String arg2Name, BiFunction<T1, T2, TemplateBody> body) {
|
||||
return new Template.TwoArgs<>(arg1Name, arg2Name, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with three arguments.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* Good practice but not enforced: {@code arg1Name}, {@code arg2Name}, and {@code arg3Name} should match the lambda argument names.
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @param <T1> Type of the first argument.
|
||||
* @param arg1Name The name of the first argument for hashtag replacement.
|
||||
* @param <T2> Type of the second argument.
|
||||
* @param arg2Name The name of the second argument for hashtag replacement.
|
||||
* @param <T3> Type of the third argument.
|
||||
* @param arg3Name The name of the third argument for hashtag replacement.
|
||||
* @return A {@link Template} with three arguments.
|
||||
*/
|
||||
static <T1, T2, T3> Template.ThreeArgs<T1, T2, T3> make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction<T1, T2, T3, TemplateBody> body) {
|
||||
return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TemplateBody} from a list of tokens, which can be {@link String}s,
|
||||
* boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), any {@link Token},
|
||||
* or {@link List}s of any of these.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> body(
|
||||
* """
|
||||
* Multi-line string
|
||||
* """,
|
||||
* "normal string ", Integer.valueOf(3), 3, Float.valueOf(1.5f), 1.5f,
|
||||
* List.of("abc", "def"),
|
||||
* nestedTemplate.asToken(42)
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param tokens A list of tokens, which can be {@link String}s, boxed primitive types
|
||||
* (for example {@link Integer}), any {@link Token}, or {@link List}s
|
||||
* of any of these.
|
||||
* @return The {@link TemplateBody} which captures the list of validated {@link Token}s.
|
||||
* @throws IllegalArgumentException if the list of tokens contains an unexpected object.
|
||||
*/
|
||||
static TemplateBody body(Object... tokens) {
|
||||
return new TemplateBody(Token.parse(tokens));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the dollar replacement of the {@code 'name'} for the
|
||||
* current Template that is being instantiated. It returns the same
|
||||
* dollar replacement as the string use {@code "$name"}.
|
||||
*
|
||||
* <p>
|
||||
* Here is an example where a Template creates a local variable {@code 'var'},
|
||||
* with an implicit dollar replacement, and then captures that dollar replacement
|
||||
* using {@link #$} for the use inside a nested template.
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> body(
|
||||
* """
|
||||
* int $var = 42;
|
||||
* """,
|
||||
* otherTemplate.asToken($("var"))
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param name The {@link String} name of the name.
|
||||
* @return The dollar replacement for the {@code 'name'}.
|
||||
*/
|
||||
static String $(String name) {
|
||||
return Renderer.getCurrent().$(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a hashtag replacement for {@code "#key"}, with a specific value.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make("a", (Integer a) -> body(
|
||||
* let("b", a * 5),
|
||||
* """
|
||||
* System.out.println("Use a and b with hashtag replacement: #a and #b");
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param key Name for the hashtag replacement.
|
||||
* @param value The value that the hashtag is replaced with.
|
||||
* @return A token that does nothing, so that the {@link #let} can easily be put in a list of tokens
|
||||
* inside a {@link Template#body}.
|
||||
* @throws RendererException if there is a duplicate hashtag {@code key}.
|
||||
*/
|
||||
static Token let(String key, Object value) {
|
||||
Renderer.getCurrent().addHashtagReplacement(key, value);
|
||||
return new NothingToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a hashtag replacement for {@code "#key"}, with a specific value, which is also captured
|
||||
* by the provided {@code function} with type {@code <T>}.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make("a", (Integer a) -> let("b", a * 2, (Integer b) -> body(
|
||||
* """
|
||||
* System.out.println("Use a and b with hashtag replacement: #a and #b");
|
||||
* """,
|
||||
* "System.out.println(\"Use a and b as capture variables:\"" + a + " and " + b + ");\n"
|
||||
* )));
|
||||
* }
|
||||
*
|
||||
* @param key Name for the hashtag replacement.
|
||||
* @param value The value that the hashtag is replaced with.
|
||||
* @param <T> The type of the value.
|
||||
* @param function The function that is applied with the provided {@code value}.
|
||||
* @return A {@link TemplateBody}.
|
||||
* @throws RendererException if there is a duplicate hashtag {@code key}.
|
||||
*/
|
||||
static <T> TemplateBody let(String key, T value, Function<T, TemplateBody> function) {
|
||||
Renderer.getCurrent().addHashtagReplacement(key, value);
|
||||
return function.apply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default amount of fuel for Template rendering. It guides the nesting depth of Templates. Can be changed when
|
||||
* rendering a template with {@code render(fuel)} (e.g. {@link ZeroArgs#render(float)}).
|
||||
*/
|
||||
float DEFAULT_FUEL = 100.0f;
|
||||
|
||||
/**
|
||||
* The default amount of fuel spent per Template. It is subtracted from the current {@link #fuel} at every
|
||||
* nesting level, and once the {@link #fuel} reaches zero, the nesting is supposed to terminate. Can be changed
|
||||
* with {@link #setFuelCost(float)} inside {@link #body(Object...)}.
|
||||
*/
|
||||
float DEFAULT_FUEL_COST = 10.0f;
|
||||
|
||||
/**
|
||||
* The current remaining fuel for nested Templates. Every level of Template nesting
|
||||
* subtracts a certain amount of fuel, and when it reaches zero, Templates are supposed to
|
||||
* stop nesting, if possible. This is not a hard rule, but a guide, and a mechanism to ensure
|
||||
* termination in recursive Template instantiations.
|
||||
*
|
||||
* <p>
|
||||
* Example of a recursive Template, which checks the remaining {@link #fuel} at every level,
|
||||
* and terminates if it reaches zero. It also demonstrates the use of {@link TemplateBinding} for
|
||||
* the recursive use of Templates. We {@link Template.OneArg#render} with {@code 30} total fuel,
|
||||
* and spend {@code 5} fuel at each recursion level.
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var binding = new TemplateBinding<Template.OneArg<Integer>>();
|
||||
* var template = Template.make("depth", (Integer depth) -> body(
|
||||
* setFuelCost(5.0f),
|
||||
* let("fuel", fuel()),
|
||||
* """
|
||||
* System.out.println("Currently at depth #depth with fuel #fuel");
|
||||
* """,
|
||||
* (fuel() > 0) ? binding.get().asToken(depth + 1) :
|
||||
* "// terminate\n"
|
||||
* ));
|
||||
* binding.bind(template);
|
||||
* String code = template.render(30.0f, 0);
|
||||
* }
|
||||
*
|
||||
* @return The amount of fuel left for nested Template use.
|
||||
*/
|
||||
static float fuel() {
|
||||
return Renderer.getCurrent().fuel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the amount of fuel used for the current Template, where the default is
|
||||
* {@link Template#DEFAULT_FUEL_COST}.
|
||||
*
|
||||
* @param fuelCost The amount of fuel used for the current Template.
|
||||
* @return A token for convenient use in {@link Template#body}.
|
||||
*/
|
||||
static Token setFuelCost(float fuelCost) {
|
||||
Renderer.getCurrent().setFuelCost(fuelCost);
|
||||
return new NothingToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link DataName} in the current scope, that is the innermost of either
|
||||
* {@link Template#body} or {@link Hook#anchor}.
|
||||
*
|
||||
* @param name The name of the {@link DataName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link DataName}.
|
||||
* @param mutability Indicates if the {@link DataName} is to be mutable or immutable,
|
||||
* i.e. if we intend to use the {@link DataName} only for reading
|
||||
* or if we also allow it to be mutated.
|
||||
* @param weight The weight of the {@link DataName}, which correlates to the probability
|
||||
* of this {@link DataName} being chosen when we sample.
|
||||
* Must be a value from 1 to 1000.
|
||||
* @return The token that performs the defining action.
|
||||
*/
|
||||
static Token addDataName(String name, DataName.Type type, DataName.Mutability mutability, int weight) {
|
||||
if (mutability != DataName.Mutability.MUTABLE &&
|
||||
mutability != DataName.Mutability.IMMUTABLE) {
|
||||
throw new IllegalArgumentException("Unexpected mutability: " + mutability);
|
||||
}
|
||||
boolean mutable = mutability == DataName.Mutability.MUTABLE;
|
||||
if (weight <= 0 || 1000 < weight) {
|
||||
throw new IllegalArgumentException("Unexpected weight: " + weight);
|
||||
}
|
||||
return new AddNameToken(new DataName(name, type, mutable, weight));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link DataName} in the current scope, that is the innermost of either
|
||||
* {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1.
|
||||
*
|
||||
* @param name The name of the {@link DataName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link DataName}.
|
||||
* @param mutability Indicates if the {@link DataName} is to be mutable or immutable,
|
||||
* i.e. if we intend to use the {@link DataName} only for reading
|
||||
* or if we also allow it to be mutated.
|
||||
* @return The token that performs the defining action.
|
||||
*/
|
||||
static Token addDataName(String name, DataName.Type type, DataName.Mutability mutability) {
|
||||
return addDataName(name, type, mutability, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the set of {@link DataName}s, for sampling, counting, etc.
|
||||
*
|
||||
* @param mutability Indicates if we only sample from mutable, immutable or either {@link DataName}s.
|
||||
* @return A view on the {@link DataName}s, on which we can sample, count, etc.
|
||||
*/
|
||||
static DataName.FilteredSet dataNames(DataName.Mutability mutability) {
|
||||
return new DataName.FilteredSet(mutability);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link StructuralName} in the current scope, that is the innermost of either
|
||||
* {@link Template#body} or {@link Hook#anchor}.
|
||||
*
|
||||
* @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link StructuralName}.
|
||||
* @param weight The weight of the {@link StructuralName}, which correlates to the probability
|
||||
* of this {@link StructuralName} being chosen when we sample.
|
||||
* Must be a value from 1 to 1000.
|
||||
* @return The token that performs the defining action.
|
||||
*/
|
||||
static Token addStructuralName(String name, StructuralName.Type type, int weight) {
|
||||
if (weight <= 0 || 1000 < weight) {
|
||||
throw new IllegalArgumentException("Unexpected weight: " + weight);
|
||||
}
|
||||
return new AddNameToken(new StructuralName(name, type, weight));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link StructuralName} in the current scope, that is the innermost of either
|
||||
* {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1.
|
||||
*
|
||||
* @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link StructuralName}.
|
||||
* @return The token that performs the defining action.
|
||||
*/
|
||||
static Token addStructuralName(String name, StructuralName.Type type) {
|
||||
return addStructuralName(name, type, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the set of {@link StructuralName}s, for sampling, counting, etc.
|
||||
*
|
||||
* @return A view on the {@link StructuralName}s, on which we can sample, count, etc.
|
||||
*/
|
||||
static StructuralName.FilteredSet structuralNames() {
|
||||
return new StructuralName.FilteredSet();
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
/**
|
||||
* To facilitate recursive uses of Templates, for example where a template uses
|
||||
* itself and needs to be referenced before it is fully defined,
|
||||
* one can use the indirection of a {@link TemplateBinding}. The {@link TemplateBinding}
|
||||
* is allocated first without any Template bound to it yet. At this stage,
|
||||
* it can be used with {@link #get} inside a Template. Later, we can {@link #bind}
|
||||
* a Template to the binding, such that {@link #get} returns that bound
|
||||
* Template.
|
||||
*
|
||||
* @param <T> Type of the template.
|
||||
*/
|
||||
public class TemplateBinding<T extends Template> {
|
||||
private T template = null;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TemplateBinding} that has no Template bound to it yet.
|
||||
*/
|
||||
public TemplateBinding() {}
|
||||
|
||||
/**
|
||||
* Retrieve the Template that was previously bound to the binding.
|
||||
*
|
||||
* @return The Template that was previously bound with {@link #bind}.
|
||||
* @throws RendererException if no Template was bound yet.
|
||||
*/
|
||||
public T get() {
|
||||
if (template == null) {
|
||||
throw new RendererException("Cannot 'get' before 'bind'.");
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a Template for future reference using {@link #get}.
|
||||
*
|
||||
* @param template The Template to be bound.
|
||||
* @throws RendererException if a Template was already bound.
|
||||
*/
|
||||
public void bind(T template) {
|
||||
if (this.template != null) {
|
||||
throw new RendererException("Duplicate 'bind' not allowed.");
|
||||
}
|
||||
this.template = template;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Template generates a {@link TemplateBody}, which is a list of {@link Token}s,
|
||||
* which are then later rendered to {@link String}s.
|
||||
*
|
||||
* @param tokens The list of {@link Token}s that are later rendered to {@link String}s.
|
||||
*/
|
||||
public record TemplateBody(List<Token> tokens) {}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The {@link TemplateFrame} is the frame for a {@link Template}, i.e. the corresponding
|
||||
* {@link TemplateToken}. It ensures that each template use has its own unique {@link #id}
|
||||
* used to deconflict names using {@link Template#$}. It also has a set of hashtag
|
||||
* replacements, which combine the key-value pairs from the template argument and the
|
||||
* {@link Template#let} definitions. The {@link #parent} relationship provides a trace
|
||||
* for the use chain of templates. The {@link #fuel} is reduced over this chain, to give
|
||||
* a heuristic on how much time is spent on the code from the template corresponding to
|
||||
* the frame, and to give a termination criterion to avoid nesting templates too deeply.
|
||||
*
|
||||
* <p>
|
||||
* See also {@link CodeFrame} for more explanations about the frames.
|
||||
*/
|
||||
class TemplateFrame {
|
||||
final TemplateFrame parent;
|
||||
private final int id;
|
||||
private final Map<String, String> hashtagReplacements = new HashMap<>();
|
||||
final float fuel;
|
||||
private float fuelCost;
|
||||
|
||||
public static TemplateFrame makeBase(int id, float fuel) {
|
||||
return new TemplateFrame(null, id, fuel, 0.0f);
|
||||
}
|
||||
|
||||
public static TemplateFrame make(TemplateFrame parent, int id) {
|
||||
return new TemplateFrame(parent, id, parent.fuel - parent.fuelCost, Template.DEFAULT_FUEL_COST);
|
||||
}
|
||||
|
||||
private TemplateFrame(TemplateFrame parent, int id, float fuel, float fuelCost) {
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
this.fuel = fuel;
|
||||
this.fuelCost = fuelCost;
|
||||
}
|
||||
|
||||
public String $(String name) {
|
||||
if (name == null) {
|
||||
throw new RendererException("A '$' name should not be null.");
|
||||
}
|
||||
if (!Renderer.isValidHashtagOrDollarName(name)) {
|
||||
throw new RendererException("Is not a valid '$' name: '" + name + "'.");
|
||||
}
|
||||
return name + "_" + id;
|
||||
}
|
||||
|
||||
void addHashtagReplacement(String key, String value) {
|
||||
if (key == null) {
|
||||
throw new RendererException("A hashtag replacement should not be null.");
|
||||
}
|
||||
if (!Renderer.isValidHashtagOrDollarName(key)) {
|
||||
throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'.");
|
||||
}
|
||||
if (hashtagReplacements.putIfAbsent(key, value) != null) {
|
||||
throw new RendererException("Duplicate hashtag replacement for #" + key);
|
||||
}
|
||||
}
|
||||
|
||||
String getHashtagReplacement(String key) {
|
||||
if (!Renderer.isValidHashtagOrDollarName(key)) {
|
||||
throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'.");
|
||||
}
|
||||
if (hashtagReplacements.containsKey(key)) {
|
||||
return hashtagReplacements.get(key);
|
||||
}
|
||||
throw new RendererException("Missing hashtag replacement for #" + key);
|
||||
}
|
||||
|
||||
void setFuelCost(float fuelCost) {
|
||||
this.fuelCost = fuelCost;
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
/**
|
||||
* Represents a tokenized {@link Template} (after calling {@code asToken()}) ready for
|
||||
* instantiation either as a {@link Token} inside another {@link Template} or as
|
||||
* a {@link String} with {@link #render}.
|
||||
*/
|
||||
public sealed abstract class TemplateToken implements Token
|
||||
permits TemplateToken.ZeroArgs,
|
||||
TemplateToken.OneArg,
|
||||
TemplateToken.TwoArgs,
|
||||
TemplateToken.ThreeArgs
|
||||
{
|
||||
private TemplateToken() {}
|
||||
|
||||
/**
|
||||
* Represents a tokenized zero-argument {@link Template} ready for instantiation
|
||||
* either as a {@link Token} inside another {@link Template} or as a {@link String}
|
||||
* with {@link #render}.
|
||||
*/
|
||||
static final class ZeroArgs extends TemplateToken implements Token {
|
||||
private final Template.ZeroArgs zeroArgs;
|
||||
|
||||
ZeroArgs(Template.ZeroArgs zeroArgs) {
|
||||
this.zeroArgs = zeroArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
return zeroArgs.instantiate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArguments(ArgumentVisitor visitor) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a tokenized one-argument {@link Template}, already filled with arguments, ready for
|
||||
* instantiation either as a {@link Token} inside another {@link Template} or as a {@link String}
|
||||
* with {@link #render}.
|
||||
*
|
||||
* @param <T1> The type of the (first) argument.
|
||||
*/
|
||||
static final class OneArg<T1> extends TemplateToken implements Token {
|
||||
private final Template.OneArg<T1> oneArgs;
|
||||
private final T1 arg1;
|
||||
|
||||
OneArg(Template.OneArg<T1> oneArgs, T1 arg1) {
|
||||
this.oneArgs = oneArgs;
|
||||
this.arg1 = arg1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
return oneArgs.instantiate(arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArguments(ArgumentVisitor visitor) {
|
||||
visitor.visit(oneArgs.arg1Name(), arg1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a tokenized two-argument {@link Template}, already filled with arguments, ready for
|
||||
* instantiation either as a {@link Token} inside another {@link Template} or as a {@link String}
|
||||
* with {@link #render}.
|
||||
*
|
||||
* @param <T1> The type of the first argument.
|
||||
* @param <T2> The type of the second argument.
|
||||
*/
|
||||
static final class TwoArgs<T1, T2> extends TemplateToken implements Token {
|
||||
private final Template.TwoArgs<T1, T2> twoArgs;
|
||||
private final T1 arg1;
|
||||
private final T2 arg2;
|
||||
|
||||
TwoArgs(Template.TwoArgs<T1, T2> twoArgs, T1 arg1, T2 arg2) {
|
||||
this.twoArgs = twoArgs;
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
return twoArgs.instantiate(arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArguments(ArgumentVisitor visitor) {
|
||||
visitor.visit(twoArgs.arg1Name(), arg1);
|
||||
visitor.visit(twoArgs.arg2Name(), arg2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a tokenized three-argument {@link TemplateToken}, already filled with arguments, ready for
|
||||
* instantiation either as a {@link Token} inside another {@link Template} or as a {@link String}
|
||||
* with {@link #render}.
|
||||
*
|
||||
* @param <T1> The type of the first argument.
|
||||
* @param <T2> The type of the second argument.
|
||||
* @param <T3> The type of the second argument.
|
||||
*/
|
||||
static final class ThreeArgs<T1, T2, T3> extends TemplateToken implements Token {
|
||||
private final Template.ThreeArgs<T1, T2, T3> threeArgs;
|
||||
private final T1 arg1;
|
||||
private final T2 arg2;
|
||||
private final T3 arg3;
|
||||
|
||||
ThreeArgs(Template.ThreeArgs<T1, T2, T3> threeArgs, T1 arg1, T2 arg2, T3 arg3) {
|
||||
this.threeArgs = threeArgs;
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
this.arg3 = arg3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
return threeArgs.instantiate(arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArguments(ArgumentVisitor visitor) {
|
||||
visitor.visit(threeArgs.arg1Name(), arg1);
|
||||
visitor.visit(threeArgs.arg2Name(), arg2);
|
||||
visitor.visit(threeArgs.arg3Name(), arg3);
|
||||
}
|
||||
}
|
||||
|
||||
abstract TemplateBody instantiate();
|
||||
|
||||
@FunctionalInterface
|
||||
interface ArgumentVisitor {
|
||||
void visit(String name, Object value);
|
||||
}
|
||||
|
||||
abstract void visitArguments(ArgumentVisitor visitor);
|
||||
|
||||
final String render() {
|
||||
return Renderer.render(this);
|
||||
}
|
||||
|
||||
final String render(float fuel) {
|
||||
return Renderer.render(this, fuel);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either
|
||||
* {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed
|
||||
* and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens
|
||||
* {@link List}s.
|
||||
*/
|
||||
sealed interface Token permits StringToken,
|
||||
TemplateToken,
|
||||
TemplateToken.ZeroArgs,
|
||||
TemplateToken.OneArg,
|
||||
TemplateToken.TwoArgs,
|
||||
TemplateToken.ThreeArgs,
|
||||
HookAnchorToken,
|
||||
HookInsertToken,
|
||||
AddNameToken,
|
||||
NothingToken
|
||||
{
|
||||
static List<Token> parse(Object[] objects) {
|
||||
if (objects == null) {
|
||||
throw new IllegalArgumentException("Unexpected tokens: null");
|
||||
}
|
||||
List<Token> outputList = new ArrayList<>();
|
||||
parseToken(Arrays.asList(objects), outputList);
|
||||
return outputList;
|
||||
}
|
||||
|
||||
private static void parseList(List<?> inputList, List<Token> outputList) {
|
||||
for (Object o : inputList) {
|
||||
parseToken(o, outputList);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseToken(Object o, List<Token> outputList) {
|
||||
if (o == null) {
|
||||
throw new IllegalArgumentException("Unexpected token: null");
|
||||
}
|
||||
switch (o) {
|
||||
case Token t -> outputList.add(t);
|
||||
case String s -> outputList.add(new StringToken(Renderer.format(s)));
|
||||
case Integer s -> outputList.add(new StringToken(Renderer.format(s)));
|
||||
case Long s -> outputList.add(new StringToken(Renderer.format(s)));
|
||||
case Double s -> outputList.add(new StringToken(Renderer.format(s)));
|
||||
case Float s -> outputList.add(new StringToken(Renderer.format(s)));
|
||||
case Boolean s -> outputList.add(new StringToken(Renderer.format(s)));
|
||||
case List<?> l -> parseList(l, outputList);
|
||||
default -> throw new IllegalArgumentException("Unexpected token: " + o);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package compiler.lib.template_framework.library;
|
||||
|
||||
import compiler.lib.template_framework.Hook;
|
||||
|
||||
/**
|
||||
* Provides a hook for class and method scopes, to be used in Templates.
|
||||
*/
|
||||
public final class Hooks {
|
||||
private Hooks() {} // Avoid instantiation and need for documentation.
|
||||
|
||||
/**
|
||||
* Template {@link Hook} used by the Template Library for class scopes, to insert
|
||||
* fields and methods.
|
||||
*/
|
||||
public static final Hook CLASS_HOOK = new Hook("Class");
|
||||
|
||||
/**
|
||||
* Template {@link Hook} used by the Template Library for method scopes, to insert
|
||||
* local variables, and computations for local variables at the beginning of a
|
||||
* method.
|
||||
*/
|
||||
public static final Hook METHOD_HOOK = new Hook("Method");
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8344942
|
||||
* @summary Test simple use of Templates with the Compile Framework.
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @library /test/lib /
|
||||
* @compile ../../../compiler/lib/ir_framework/TestFramework.java
|
||||
* @compile ../../../compiler/lib/verify/Verify.java
|
||||
* @run main template_framework.examples.TestAdvanced
|
||||
*/
|
||||
|
||||
package template_framework.examples;
|
||||
|
||||
import java.util.List;
|
||||
import jdk.test.lib.Utils;
|
||||
|
||||
import compiler.lib.generators.Generator;
|
||||
import compiler.lib.generators.Generators;
|
||||
import compiler.lib.generators.RestrictableGenerator;
|
||||
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
|
||||
/**
|
||||
* This is a basic example for Templates, using them to cover a list of test variants.
|
||||
* <p>
|
||||
* The "@compile" command for JTREG is required so that the frameworks used in the Template code
|
||||
* are compiled and available for the Test-VM.
|
||||
* <p>
|
||||
* Additionally, we must set the classpath for the Test-VM, so that it has access to all compiled
|
||||
* classes (see {@link CompileFramework#getEscapedClassPathOfCompiledClasses}).
|
||||
*/
|
||||
public class TestAdvanced {
|
||||
public static final RestrictableGenerator<Integer> GEN_BYTE = Generators.G.safeRestrict(Generators.G.ints(), Byte.MIN_VALUE, Byte.MAX_VALUE);
|
||||
public static final RestrictableGenerator<Integer> GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE);
|
||||
public static final RestrictableGenerator<Integer> GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
public static final RestrictableGenerator<Integer> GEN_INT = Generators.G.ints();
|
||||
public static final RestrictableGenerator<Long> GEN_LONG = Generators.G.longs();
|
||||
public static final Generator<Float> GEN_FLOAT = Generators.G.floats();
|
||||
public static final Generator<Double> GEN_DOUBLE = Generators.G.doubles();
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Create a new CompileFramework instance.
|
||||
CompileFramework comp = new CompileFramework();
|
||||
|
||||
// Add a java source file.
|
||||
comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp));
|
||||
|
||||
// Compile the source file.
|
||||
comp.compile();
|
||||
|
||||
// Object ret = p.xyz.InnerTest.main();
|
||||
comp.invoke("p.xyz.InnerTest", "main", new Object[] {});
|
||||
}
|
||||
|
||||
interface MyGenerator {
|
||||
Object next();
|
||||
}
|
||||
|
||||
record Type(String name, MyGenerator generator, List<String> operators) {}
|
||||
|
||||
// Generate a source Java file as String
|
||||
public static String generate(CompileFramework comp) {
|
||||
|
||||
// The test template:
|
||||
// - For a chosen type, operator, and generator.
|
||||
// - The variable name "GOLD" and the test name "test" would get conflicts
|
||||
// if we instantiate the template multiple times. Thus, we use the $ prefix
|
||||
// so that the Template Framework can replace the names and make them unique
|
||||
// for each Template instantiation.
|
||||
// - The GOLD value is computed at the beginning, hopefully by the interpreter.
|
||||
// - The test method is eventually compiled, and the values are verified by the
|
||||
// check method.
|
||||
var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body(
|
||||
let("con1", generator.next()),
|
||||
let("con2", generator.next()),
|
||||
"""
|
||||
// #typeName #operator #con1 #con2
|
||||
public static #typeName $GOLD = $test();
|
||||
|
||||
@Test
|
||||
public static #typeName $test() {
|
||||
return (#typeName)(#con1 #operator #con2);
|
||||
}
|
||||
|
||||
@Check(test = "$test")
|
||||
public static void $check(#typeName result) {
|
||||
Verify.checkEQ(result, $GOLD);
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
// Template for the Class.
|
||||
var classTemplate = Template.make("types", (List<Type> types) -> body(
|
||||
let("classpath", comp.getEscapedClassPathOfCompiledClasses()),
|
||||
"""
|
||||
package p.xyz;
|
||||
|
||||
import compiler.lib.ir_framework.*;
|
||||
import compiler.lib.verify.*;
|
||||
|
||||
public class InnerTest {
|
||||
public static void main() {
|
||||
TestFramework framework = new TestFramework(InnerTest.class);
|
||||
// Set the classpath, so that the TestFramework test VM knows where
|
||||
// the CompileFramework put the class files of the compiled source code.
|
||||
framework.addFlags("-classpath", "#classpath");
|
||||
framework.start();
|
||||
}
|
||||
|
||||
""",
|
||||
// Call the testTemplate for each type and operator, generating a
|
||||
// list of lists of TemplateToken:
|
||||
types.stream().map((Type type) ->
|
||||
type.operators().stream().map((String operator) ->
|
||||
testTemplate.asToken(type.name(), operator, type.generator())).toList()
|
||||
).toList(),
|
||||
"""
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
// For each type, we choose a list of operators that do not throw exceptions.
|
||||
List<Type> types = List.of(
|
||||
new Type("byte", GEN_BYTE::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
new Type("char", GEN_CHAR::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
new Type("short", GEN_SHORT::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
new Type("int", GEN_INT::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
new Type("long", GEN_LONG::next, List.of("+", "-", "*", "&", "|", "^")),
|
||||
new Type("float", GEN_FLOAT::next, List.of("+", "-", "*", "/")),
|
||||
new Type("double", GEN_DOUBLE::next, List.of("+", "-", "*", "/"))
|
||||
);
|
||||
|
||||
// Use the template with one argument and render it to a String.
|
||||
return classTemplate.render(types);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8344942
|
||||
* @summary Test simple use of Templates with the Compile Framework.
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @library /test/lib /
|
||||
* @run main template_framework.examples.TestSimple
|
||||
*/
|
||||
|
||||
package template_framework.examples;
|
||||
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
|
||||
public class TestSimple {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Create a new CompileFramework instance.
|
||||
CompileFramework comp = new CompileFramework();
|
||||
|
||||
// Add a java source file.
|
||||
comp.addJavaSourceCode("p.xyz.InnerTest", generate());
|
||||
|
||||
// Compile the source file.
|
||||
comp.compile();
|
||||
|
||||
// Object ret = p.xyz.InnerTest.test();
|
||||
Object ret = comp.invoke("p.xyz.InnerTest", "test", new Object[] {});
|
||||
System.out.println("res: " + ret);
|
||||
|
||||
// Check that the return value is the sum of the two arguments.
|
||||
if ((42 + 7) != (int)ret) {
|
||||
throw new RuntimeException("Unexpected result");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a source Java file as String
|
||||
public static String generate() {
|
||||
// Create a Template with two arguments.
|
||||
var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> body(
|
||||
"""
|
||||
package p.xyz;
|
||||
public class InnerTest {
|
||||
public static int test() {
|
||||
return #arg1 + #arg2;
|
||||
}
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
// Use the template with two arguments, and render it to a String.
|
||||
return template.render(42, "7");
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8344942
|
||||
* @summary Test formatting of Integer, Long, Float and Double.
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @library /test/lib /
|
||||
* @run main template_framework.tests.TestFormat
|
||||
*/
|
||||
|
||||
package template_framework.tests;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.generators.*;
|
||||
import compiler.lib.verify.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
|
||||
public class TestFormat {
|
||||
record FormatInfo(int id, String type, Object value) {}
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<FormatInfo> list = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
int v = Generators.G.ints().next();
|
||||
list.add(new FormatInfo(i, "int", v));
|
||||
}
|
||||
|
||||
for (int i = 1000; i < 2000; i++) {
|
||||
long v = Generators.G.longs().next();
|
||||
list.add(new FormatInfo(i, "long", v));
|
||||
}
|
||||
|
||||
for (int i = 2000; i < 3000; i++) {
|
||||
float v = Generators.G.floats().next();
|
||||
list.add(new FormatInfo(i, "float", v));
|
||||
}
|
||||
|
||||
for (int i = 3000; i < 4000; i++) {
|
||||
double v = Generators.G.doubles().next();
|
||||
list.add(new FormatInfo(i, "double", v));
|
||||
}
|
||||
|
||||
CompileFramework comp = new CompileFramework();
|
||||
comp.addJavaSourceCode("p.xyz.InnerTest", generate(list));
|
||||
comp.compile();
|
||||
|
||||
// Run each of the "get" methods, and check the result.
|
||||
for (FormatInfo info : list) {
|
||||
Object ret1 = comp.invoke("p.xyz.InnerTest", "get_let_" + info.id, new Object[] {});
|
||||
Object ret2 = comp.invoke("p.xyz.InnerTest", "get_token_" + info.id, new Object[] {});
|
||||
System.out.println("id: " + info.id + " -> " + info.value + " == " + ret1 + " == " + ret2);
|
||||
Verify.checkEQ(ret1, info.value);
|
||||
Verify.checkEQ(ret2, info.value);
|
||||
}
|
||||
}
|
||||
|
||||
private static String generate(List<FormatInfo> list) {
|
||||
// Generate 2 "get" methods, one that formats via "let" (hashtag), the other via direct token.
|
||||
var template1 = Template.make("info", (FormatInfo info) -> body(
|
||||
let("id", info.id()),
|
||||
let("type", info.type()),
|
||||
let("value", info.value()),
|
||||
"""
|
||||
public static #type get_let_#id() { return #value; }
|
||||
""",
|
||||
"public static #type get_token_#id() { return ", info.value(), "; }\n"
|
||||
));
|
||||
|
||||
// For each FormatInfo in list, generate the "get" methods inside InnerTest class.
|
||||
var template2 = Template.make(() -> body(
|
||||
"""
|
||||
package p.xyz;
|
||||
public class InnerTest {
|
||||
""",
|
||||
list.stream().map(info -> template1.asToken(info)).toList(),
|
||||
"""
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
return template2.render();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user