mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8367531: Template Framework: use scopes and tokens instead of misbehaving immediate-return-queries
Co-authored-by: Christian Hagedorn <chagedorn@openjdk.org> Reviewed-by: rcastanedalo, mhaessig, chagedorn
This commit is contained in:
parent
6fc8e49980
commit
b41146cd1e
@ -60,7 +60,7 @@ public class TestMethodArguments {
|
||||
: IntStream.range(0, numberOfArguments)
|
||||
.mapToObj(i -> "x" + i)
|
||||
.collect(Collectors.joining(" + "));
|
||||
return Template.make(() -> Template.body(
|
||||
return Template.make(() -> Template.scope(
|
||||
Template.let("type", type.name()),
|
||||
Template.let("boxedType", type.boxedTypeName()),
|
||||
Template.let("arguments", arguments),
|
||||
@ -115,7 +115,7 @@ public class TestMethodArguments {
|
||||
tests.add(generateTest(CodeGenerationDataNameType.longs(), i / 2).asToken());
|
||||
tests.add(generateTest(CodeGenerationDataNameType.doubles(), i / 2).asToken());
|
||||
}
|
||||
return Template.make(() -> Template.body(
|
||||
return Template.make(() -> Template.scope(
|
||||
Template.let("classpath", comp.getEscapedClassPathOfCompiledClasses()),
|
||||
"""
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -45,7 +45,7 @@ import java.util.stream.IntStream;
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import static compiler.lib.template_framework.Template.$;
|
||||
import compiler.lib.template_framework.library.CodeGenerationDataNameType;
|
||||
@ -99,7 +99,7 @@ public class ExpressionFuzzer {
|
||||
|
||||
// Create the body for the test. We use it twice: compiled and reference.
|
||||
// Execute the expression and catch expected Exceptions.
|
||||
var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List<Object> arguments, String checksum) -> body(
|
||||
var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List<Object> arguments, String checksum) -> scope(
|
||||
"""
|
||||
try {
|
||||
""",
|
||||
@ -167,14 +167,14 @@ public class ExpressionFuzzer {
|
||||
default -> throw new RuntimeException("not handled: " + type.name());
|
||||
};
|
||||
StringPair cmp = cmps.get(RANDOM.nextInt(cmps.size()));
|
||||
return body(
|
||||
return scope(
|
||||
", ", cmp.s0(), type.con(), cmp.s1()
|
||||
);
|
||||
});
|
||||
|
||||
// Checksum method: returns not just the value, but also does some range / bit checks.
|
||||
// This gives us enhanced verification on the range / bits of the result type.
|
||||
var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> body(
|
||||
var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> scope(
|
||||
let("returnType", expression.returnType),
|
||||
"""
|
||||
@ForceInline
|
||||
@ -201,7 +201,7 @@ public class ExpressionFuzzer {
|
||||
|
||||
// We need to prepare some random values to pass into the test method. We generate the values
|
||||
// once, and pass the same values into both the compiled and reference method.
|
||||
var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body(
|
||||
var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope(
|
||||
"#type #name = ",
|
||||
(type instanceof PrimitiveType pt) ? pt.callLibraryRNG() : type.con(),
|
||||
";\n"
|
||||
@ -213,7 +213,7 @@ public class ExpressionFuzzer {
|
||||
//
|
||||
// To ensure that both the compiled and reference method use the same constraint, we put
|
||||
// the computation in a ForceInline method.
|
||||
var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body(
|
||||
var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope(
|
||||
"""
|
||||
@ForceInline
|
||||
public static #type constrain_#name(#type v) {
|
||||
@ -247,7 +247,7 @@ public class ExpressionFuzzer {
|
||||
"""
|
||||
));
|
||||
|
||||
var constrainArgumentTemplate = Template.make("name", (String name) -> body(
|
||||
var constrainArgumentTemplate = Template.make("name", (String name) -> scope(
|
||||
"""
|
||||
#name = constrain_#name(#name);
|
||||
"""
|
||||
@ -279,7 +279,7 @@ public class ExpressionFuzzer {
|
||||
}
|
||||
}
|
||||
}
|
||||
return body(
|
||||
return scope(
|
||||
let("methodArguments",
|
||||
methodArguments.stream().map(ma -> ma.name).collect(Collectors.joining(", "))),
|
||||
let("methodArgumentsWithTypes",
|
||||
|
||||
@ -23,4 +23,8 @@
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
/**
|
||||
* Represents the addition of the specified {@link Name} to the current scope,
|
||||
* or an outer scope if the inner scope is transparent to {@link Name}s.
|
||||
*/
|
||||
record AddNameToken(Name name) implements Token {}
|
||||
|
||||
@ -29,22 +29,96 @@ 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'}
|
||||
* The {@link CodeFrame} represents a frame (i.e. scope) of generated code by appending {@link Code} to the {@link #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.
|
||||
* {@link Template#addDataName}. {@link Hook}s can be added to a code frame, which allows code to be inserted at that
|
||||
* location later.
|
||||
*
|
||||
* <p>
|
||||
* On the other hand, each {@link TemplateFrame} represents the frame (or scope) of exactly one use of a
|
||||
* Template.
|
||||
* The {@link CodeFrame} thus implements the {@link Name} non-transparency aspect of {@link ScopeToken}.
|
||||
*
|
||||
* <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}.
|
||||
* The {@link CodeFrame}s are nested relative to the order of the final rendered code. This can
|
||||
* diverge from the nesting order of the {@link Template} when using {@link Hook#insert}, where
|
||||
* the execution jumps from the current (caller) {@link CodeFrame} scope to the scope of the
|
||||
* {@link Hook#anchor}. This ensures that the {@link Name}s of the anchor scope are accessed,
|
||||
* and not the ones from the caller scope. Once the {@link Hook#insert}ion is complete, we
|
||||
* jump back to the caller {@link CodeFrame}.
|
||||
*
|
||||
* <p>
|
||||
* Note, that {@link CodeFrame}s and {@link TemplateFrame}s often go together. But they do diverge when
|
||||
* we call {@link Hook#insert}. On the {@link CodeFrame} side, the inserted scope is nested in the anchoring
|
||||
* scope, so that the inserted scope has access to the Names of the anchoring scope, and not the caller
|
||||
* scope. But the {@link TemplateFrame} of the inserted scope is nested in the caller scope, so
|
||||
* that the inserted scope has access to hashtag replacements of the caller scope, and not the
|
||||
* anchoring scope.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Below, we look at an example, and show the use of CodeFrames (c) and TemplateFrames (t).
|
||||
*
|
||||
* Explanations:
|
||||
* - Generally, every scope has a CodeFrame and a TemplateFrame. There can be multiple
|
||||
* scopes inside a Template, and so there can be multiple CodeFrames and TemplateFrames.
|
||||
* In the drawing below, we draw the frames vertically, and give each a unique id.
|
||||
* - When we nest scopes inside scopes, we create a new CodeFrame and a new TemplateFrame,
|
||||
* and so they grow the same nested structure. Example: t3 is nested inside t2 and
|
||||
* c3 is nested inside c2b.
|
||||
* - The exception to this:
|
||||
* - At a hook.anchor, there are two CodeFrames. The first one (e.g. c2a) we call the
|
||||
* hook CodeFrame, it is kept empty until we insert code to the hook. The second
|
||||
* (e.g. c2b) we call the inner CodeFrame of the anchoring, into which we keep
|
||||
* generating the code that is inside the scope of the hook.anchor.
|
||||
* - At a hook.insert, the TemplateFrame (e.g. t4) is nested into the caller (e.g. t3),
|
||||
* while the CodeFrame (e.g. c4) is nested into the anchoring CodeFrame (e.g. c2a).
|
||||
*
|
||||
* Template(
|
||||
* t1 c1
|
||||
* t1 c1
|
||||
* t1 c1 Anchoring Scope
|
||||
* t1 c1 hook.anchor(scope(
|
||||
* t1 c1 t2 c2a
|
||||
* t1 c1 t2 c2a <------ CodeFrame nesting--------+
|
||||
* t1 c1 t2 c2a with generated code |
|
||||
* t1 c1 t2 and Names |
|
||||
* t1 c1 t2 ^ |
|
||||
* t1 c1 t2 +- Two CodeFramees |
|
||||
* t1 c1 t2 v |
|
||||
* t1 c1 t2 |
|
||||
* t1 c1 t2 c2b |
|
||||
* t1 c1 t2 c2b |
|
||||
* t1 c1 t2 c2b Caller Scope |
|
||||
* t1 c1 t2 c2b ... scope( |
|
||||
* t1 c1 t2 c2b ... t3 c3 | Insertion Scope
|
||||
* t1 c1 t2 c2b ... t3 c3 | hook.insert(transparentScope(
|
||||
* t1 c1 t2 c2b ... t3 c3 | t4 c4
|
||||
* t1 c1 t2 c2b ... t3 c3 +---- t4 ----c4
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4
|
||||
* t1 c1 t2 c2b ... t3 c3 <-- TemplateFrame nesting ---t4 c4
|
||||
* t1 c1 t2 c2b ... t3 c3 with hashtag t4 c4 // t: Concerns Template Frame
|
||||
* t1 c1 t2 c2b ... t3 c3 and setFuelCost t4 c4 // c: Concerns Code Frame
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 "use hashtag #x" -> t: hashtag queried in Insertion (t4) and Caller Scope (t3)
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 c: code added to Anchoring Scope (c2a)
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 let("x", 42) -> t: hashtag definition escapes to Caller Scope (t3) because
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 Insertion Scope is transparent
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 dataNames(...)...sample() -> c: sample from Insertion (c4) and Anchoring Scope (c2a)
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 (CodeFrame nesting: c2a -> c4)
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 addDataName(...) -> c: names escape to the Caller Scope (c3) because
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4 Insertion Scope is transparent
|
||||
* t1 c1 t2 c2b ... t3 c3 t4 c4
|
||||
* t1 c1 t2 c2b ... t3 c3 ))
|
||||
* t1 c1 t2 c2b ... t3 c3
|
||||
* t1 c1 t2 c2b ... t3 c3
|
||||
* t1 c1 t2 c2b ... )
|
||||
* t1 c1 t2 c2b
|
||||
* t1 c1 t2 c2b
|
||||
* t1 c1 ))
|
||||
* t1 c1
|
||||
* t1 c1
|
||||
* )
|
||||
*
|
||||
*/
|
||||
class CodeFrame {
|
||||
public final CodeFrame parent;
|
||||
@ -78,25 +152,16 @@ class CodeFrame {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Creates a normal frame, which has a {@link #parent}. It can either be
|
||||
* transparent for names, meaning that names are added and accessed to and
|
||||
* from an outer frame. Names that are added in a transparent frame are
|
||||
* still available in the outer frames, as far out as the next non-transparent
|
||||
* frame. If a frame is non-transparent, this frame defines an inner
|
||||
* {@link NameSet}, for the names that are generated inside this frame. Once
|
||||
* this frame is exited, the names from inside this frame are not available.
|
||||
*/
|
||||
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);
|
||||
public static CodeFrame make(CodeFrame parent, boolean isTransparentForNames) {
|
||||
return new CodeFrame(parent, isTransparentForNames);
|
||||
}
|
||||
|
||||
void addString(String s) {
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link DataName}s represent things like fields and local variables, and can be added to the local
|
||||
@ -114,18 +115,36 @@ public record DataName(String name, DataName.Type type, boolean mutable, int wei
|
||||
this(mutability, null, null);
|
||||
}
|
||||
|
||||
// Wrap the FilteredSet as a Predicate.
|
||||
private record DataNamePredicate(FilteredSet fs) implements NameSet.Predicate {
|
||||
public boolean check(Name type) {
|
||||
return fs.check(type);
|
||||
}
|
||||
public String toString() {
|
||||
return fs.toString();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
return new DataNamePredicate(this);
|
||||
}
|
||||
|
||||
boolean check(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;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")";
|
||||
String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")";
|
||||
return "DataName.FilterdSet(" + mutability + msg1 + msg2 + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -173,55 +192,179 @@ public record DataName(String name, DataName.Type type, boolean mutable, int wei
|
||||
|
||||
/**
|
||||
* Samples a random {@link DataName} from the filtered set, according to the weights
|
||||
* of the contained {@link DataName}s.
|
||||
* of the contained {@link DataName}s, making the sampled {@link DataName}
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @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.
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the sampled {@link DataName}.
|
||||
* @return a token that represents the sampling and inner scope.
|
||||
* @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());
|
||||
public Token sample(Function<DataName, ScopeToken> function) {
|
||||
return new NameSampleToken<>(predicate(), null, null, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any {@link DataName}s in the filtered set.
|
||||
* Samples a random {@link DataName} from the filtered set, according to the weights
|
||||
* of the contained {@link DataName}s, and makes a hashtag replacement for both
|
||||
* the name and type of the {@link DataName}, in the current scope.
|
||||
*
|
||||
* @return Returns {@code true} iff there is at least one {@link DataName} in the filtered set.
|
||||
* <p>
|
||||
* Note, that the following two do the equivalent:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name", "type"),
|
||||
* """
|
||||
* #name #type
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> transparentScope(
|
||||
* // The "let" hashtag definitions escape the "transparentScope".
|
||||
* let("name", dn.name()),
|
||||
* let("type", dn.type())
|
||||
* )),
|
||||
* """
|
||||
* #name #type
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param name the key of the hashtag replacement for the {@link DataName} name.
|
||||
* @param type the key of the hashtag replacement for the {@link DataName} type.
|
||||
* @return a token that represents the sampling and hashtag replacement definition.
|
||||
* @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());
|
||||
public Token sampleAndLetAs(String name, String type) {
|
||||
return new NameSampleToken<DataName>(predicate(), name, type, n -> Template.transparentScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all {@link DataName}s in the filtered set.
|
||||
* Samples a random {@link DataName} from the filtered set, according to the weights
|
||||
* of the contained {@link DataName}s, and makes a hashtag replacement for the
|
||||
* name of the {@link DataName}, in the current scope.
|
||||
*
|
||||
* <p>
|
||||
* Note, that the following two do the equivalent:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name"),
|
||||
* """
|
||||
* #name
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> transparentScope(
|
||||
* // The "let" hashtag definition escape the "transparentScope".
|
||||
* let("name", dn.name())
|
||||
* )),
|
||||
* """
|
||||
* #name
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param name the key of the hashtag replacement for the {@link DataName} name.
|
||||
* @return a token that represents the sampling and hashtag replacement definition.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token sampleAndLetAs(String name) {
|
||||
return new NameSampleToken<DataName>(predicate(), name, null, n -> Template.transparentScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of {@link DataName}s in the filtered set, making the count
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the count.
|
||||
* @return a token that represents the counting and inner scope.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token count(Function<Integer, ScopeToken> function) {
|
||||
return new NameCountToken(predicate(), function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any {@link DataName}s in the filtered set, making the resulting boolean
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the boolean indicating iff there are any {@link DataName}s in the filtered set.
|
||||
* @return a token that represents the checking and inner scope.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token hasAny(Function<Boolean, ScopeToken> function) {
|
||||
return new NameHasAnyToken(predicate(), function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all {@link DataName}s in the filtered set, making the collected list
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the list of {@link DataName}.
|
||||
* @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();
|
||||
public Token toList(Function<List<DataName>, ScopeToken> function) {
|
||||
return new NamesToListToken<>(predicate(), function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided {@code function} for each {@link DataName}s in the filtered set,
|
||||
* making each of these {@link DataName}s available to a separate inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that is called to create the inner {@link ScopeToken}s
|
||||
* for each of the {@link DataName}s in the filtered set.
|
||||
* @return The token representing the for-each execution and the respective inner scopes.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token forEach(Function<DataName, ScopeToken> function) {
|
||||
return new NameForEachToken<>(predicate(), null, null, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided {@code function} for each {@link DataName}s in the filtered set,
|
||||
* making each of these {@link DataName}s available to a separate inner scope, and additionally
|
||||
* setting hashtag replacements for the {@code name} and {@code type} of the respective
|
||||
* {@link DataName}s.
|
||||
*
|
||||
* <p>
|
||||
* Note, to avoid duplication of the {@code name} and {@code type}
|
||||
* hashtag replacements, the scope created by the provided {@code function} should be
|
||||
* non-transparent to hashtag replacements, for example {@link Template#scope} or
|
||||
* {@link Template#hashtagScope}.
|
||||
*
|
||||
* @param name the key of the hashtag replacement for each individual {@link DataName} name.
|
||||
* @param type the key of the hashtag replacement for each individual {@link DataName} type.
|
||||
* @param function The {@link Function} that is called to create the inner {@link ScopeToken}s
|
||||
* for each of the {@link DataName}s in the filtereds set.
|
||||
* @return The token representing the for-each execution and the respective inner scopes.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token forEach(String name, String type, Function<DataName, ScopeToken> function) {
|
||||
return new NameForEachToken<>(predicate(), name, type, function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,59 +23,84 @@
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
* A {@link Hook} can be {@link #anchor}ed for a certain scope ({@link ScopeToken}), and that
|
||||
* anchoring stays active for any nested scope or nested {@link Template}. With {@link #insert},
|
||||
* one can insert a template ({@link TemplateToken}) or scope ({@link ScopeToken}) to where the
|
||||
* {@link Hook} was {@link #anchor}'ed. If the hook was anchored for multiple outer scopes, the
|
||||
* innermost is chosen for insertion.
|
||||
*
|
||||
* <p>
|
||||
* 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. Or one may want to reach back
|
||||
* to the beginning of a method to insert local variables that should be live for the whole method.
|
||||
*
|
||||
* <p>
|
||||
* The choice of {@link ScopeToken} is very important and powerful.
|
||||
* For example, if you want to insert a {@link DataName} to the scope of an anchor,
|
||||
* it is important that the scope of the insertion is transparent for {@link DataName}s,
|
||||
* e.g. using {@link Template#transparentScope}. In most cases, we want {@link DataName}s to escape
|
||||
* the inserted scope but not the anchor scope, so the anchor scope should be
|
||||
* non-transparent for {@link DataName}s, e.g. using {@link Template#scope}.
|
||||
* Example:
|
||||
*
|
||||
* <p>
|
||||
* {@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(
|
||||
* var template = Template.make(() -> scope(
|
||||
* """
|
||||
* public class Test {
|
||||
* """,
|
||||
* // Anchor the hook here.
|
||||
* myHook.anchor(
|
||||
* myHook.anchor(scope(
|
||||
* """
|
||||
* 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"))),
|
||||
* // Reach out to where the hook was anchored, and insert some code.
|
||||
* myHook.insert(transparentScope(
|
||||
* // The field (DataName) escapes because the inserted scope is "transparentScope"
|
||||
* addDataName($("field"), Primitives.INTS, MUTABLE),
|
||||
* """
|
||||
* public static int $field = 42;
|
||||
* """
|
||||
* )),
|
||||
* """
|
||||
* }
|
||||
* """
|
||||
* ),
|
||||
* )),
|
||||
* """
|
||||
* }
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* Note that if we use {@link #insert} with {@link Template#transparentScope}, then
|
||||
* {@link DataName}s and {@link StructuralName}s escape from the inserted scope to the
|
||||
* anchor scope, but hashtag replacements and {@link Template#setFuelCost} escape to
|
||||
* the caller, i.e. from where we inserted the scope. This makes sense if we consider
|
||||
* {@link DataName}s belonging to the structure of the generated code and the inserted
|
||||
* scope belonging to the anchor scope. On the other hand, hashtag replacements and
|
||||
* {@link Template#setFuelCost} rather belong to the code generation that happens
|
||||
* within the context of a template.
|
||||
*
|
||||
* @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'}.
|
||||
* Anchor this {@link Hook} for the provided inner scope.
|
||||
* 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.
|
||||
* @param innerScope An inner scope, for which the {@link Hook} is anchored.
|
||||
* @return A {@link Token} that captures the anchoring and the inner scope.
|
||||
*/
|
||||
public Token anchor(Object... tokens) {
|
||||
return new HookAnchorToken(this, TokenParser.parse(tokens));
|
||||
public Token anchor(ScopeToken innerScope) {
|
||||
return new HookAnchorToken(this, innerScope);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,18 +108,31 @@ public record Hook(String name) {
|
||||
* 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}.
|
||||
* @return The {@link Token} which represents the code insertion into the {@link Hook}.
|
||||
*/
|
||||
public Token insert(TemplateToken templateToken) {
|
||||
return new HookInsertToken(this, templateToken);
|
||||
return new HookInsertToken(this, Template.transparentScope(templateToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope.
|
||||
* Inserts a scope ({@link ScopeToken}) to the innermost location where this {@link Hook} was {@link #anchor}ed.
|
||||
* This could be in the same Template, or one nested further out.
|
||||
*
|
||||
* @return If the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope.
|
||||
* @param scopeToken The scope to be inserted at the {@link Hook}.
|
||||
* @return The {@link Token} which represents the code insertion into the {@link Hook}.
|
||||
*/
|
||||
public boolean isAnchored() {
|
||||
return Renderer.getCurrent().isAnchored(this);
|
||||
public Token insert(ScopeToken scopeToken) {
|
||||
return new HookInsertToken(this, scopeToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope,
|
||||
* and makes the boolean result available to an inner scope.
|
||||
*
|
||||
* @param function the function that generates the inner scope given the boolean result.
|
||||
* @return the token that represents the check and inner scope.
|
||||
*/
|
||||
public Token isAnchored(Function<Boolean, ScopeToken> function) {
|
||||
return new HookIsAnchoredToken(this, function);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,7 @@ package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
record HookAnchorToken(Hook hook, List<Token> tokens) implements Token {}
|
||||
/**
|
||||
* Represents the {@link Hook#anchor} with its inner scope.
|
||||
*/
|
||||
record HookAnchorToken(Hook hook, ScopeToken innerScope) implements Token {}
|
||||
|
||||
@ -23,4 +23,8 @@
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
record HookInsertToken(Hook hook, TemplateToken templateToken) implements Token {}
|
||||
/**
|
||||
* Represents the {@link Hook#insert} with the {@link ScopeToken} of the
|
||||
* scope that is to be inserted.
|
||||
*/
|
||||
record HookInsertToken(Hook hook, ScopeToken scopeToken) implements Token {}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
|
||||
/**
|
||||
* Represents an {@link Hook#isAnchored} query with the function that creates an inner scope
|
||||
* given the boolean answer.
|
||||
*/
|
||||
record HookIsAnchoredToken(Hook hook, Function<Boolean, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken(boolean isAnchored) {
|
||||
return function().apply(isAnchored);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
|
||||
/**
|
||||
* Represents a let (aka hashtag) definition. The hashtag replacement is active for the
|
||||
* scope ({@link ScopeToken}) that the {@code function} creates, but can escape that
|
||||
* scope if it is transparent to hashtags.
|
||||
*/
|
||||
record LetToken<T>(String key, T value, Function<T, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken() {
|
||||
return function().apply(value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
|
||||
/**
|
||||
* Represents the counting of {@link Name}s, and the function that is called
|
||||
* to create an inner scope given the count.
|
||||
*/
|
||||
record NameCountToken(
|
||||
NameSet.Predicate predicate,
|
||||
Function<Integer, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken(int count) {
|
||||
return function().apply(count);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
|
||||
/**
|
||||
* Represents the for-each execution of the provided function and (optional) hashtag replacement
|
||||
* keys for name and type of each name.
|
||||
*/
|
||||
record NameForEachToken<N>(
|
||||
NameSet.Predicate predicate,
|
||||
String name,
|
||||
String type,
|
||||
Function<N, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken(Name n) {
|
||||
return function().apply((N)n);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
|
||||
/**
|
||||
* Represents the check if there is any name and the function that is to
|
||||
* be called given the boolean value (true iff there are any names).
|
||||
*/
|
||||
record NameHasAnyToken(
|
||||
NameSet.Predicate predicate,
|
||||
Function<Boolean, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken(boolean hasAny) {
|
||||
return function().apply(hasAny);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
|
||||
/**
|
||||
* Represents the sampling of {@link Name}s, and the function that is called given
|
||||
* the sampled name, as well as the (optional) hashtag replacement keys for the
|
||||
* name and type of the sampled name, which are then available in the inner scope
|
||||
* created by the provided function.
|
||||
*/
|
||||
record NameSampleToken<N>(
|
||||
NameSet.Predicate predicate,
|
||||
String name,
|
||||
String type,
|
||||
Function<N, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken(Name n) {
|
||||
return function().apply((N)n);
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,7 @@ class NameSet {
|
||||
|
||||
interface Predicate {
|
||||
boolean check(Name type);
|
||||
String toString(); // used when sampling fails.
|
||||
}
|
||||
|
||||
NameSet(NameSet parent) {
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.Function;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the {@code toList} on a filtered name set, including the collection of the
|
||||
* names and the creation of the inner scope with the function.
|
||||
*/
|
||||
record NamesToListToken<N>(
|
||||
NameSet.Predicate predicate,
|
||||
Function<List<N>, ScopeToken> function) implements Token {
|
||||
|
||||
ScopeToken getScopeToken(List<Name> names) {
|
||||
List<N> castNames = names.stream().map(n -> (N)n).toList();
|
||||
return function().apply(castNames);
|
||||
}
|
||||
}
|
||||
@ -76,7 +76,7 @@ final class Renderer {
|
||||
* <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
|
||||
* {@link Template#scope}. 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
|
||||
@ -84,8 +84,8 @@ final class Renderer {
|
||||
* 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}.
|
||||
* Instead, the user must create a {@link TemplateToken} from the inner {@link Template}, and
|
||||
* use that {@link TemplateToken} in the {@link Template#scope} 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}.
|
||||
*
|
||||
@ -113,7 +113,7 @@ final class Renderer {
|
||||
|
||||
static Renderer getCurrent() {
|
||||
if (renderer == null) {
|
||||
throw new RendererException("A Template method such as '$', 'let', 'sample', 'count' etc. was called outside a template rendering.");
|
||||
throw new RendererException("A Template method such as '$', 'fuel', etc. was called outside a template rendering call.");
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
@ -171,26 +171,6 @@ final class Renderer {
|
||||
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}.
|
||||
@ -243,12 +223,16 @@ final class Renderer {
|
||||
}
|
||||
|
||||
private void renderTemplateToken(TemplateToken templateToken) {
|
||||
// We need a TemplateFrame in all cases, this ensures that the outermost scope of the template
|
||||
// is not transparent for hashtags and setFuelCost, and also that the id of the template is
|
||||
// unique.
|
||||
TemplateFrame templateFrame = TemplateFrame.make(currentTemplateFrame, nextTemplateFrameId++);
|
||||
currentTemplateFrame = templateFrame;
|
||||
|
||||
templateToken.visitArguments((name, value) -> addHashtagReplacement(name, format(value)));
|
||||
TemplateBody body = templateToken.instantiate();
|
||||
renderTokenList(body.tokens());
|
||||
|
||||
// If the ScopeToken is transparent to Names, then the Template is transparent to names.
|
||||
renderScopeToken(templateToken.instantiate());
|
||||
|
||||
if (currentTemplateFrame != templateFrame) {
|
||||
throw new RuntimeException("Internal error: TemplateFrame mismatch!");
|
||||
@ -256,29 +240,76 @@ final class Renderer {
|
||||
currentTemplateFrame = currentTemplateFrame.parent;
|
||||
}
|
||||
|
||||
private void renderScopeToken(ScopeToken st) {
|
||||
renderScopeToken(st, () -> {});
|
||||
}
|
||||
|
||||
private void renderScopeToken(ScopeToken st, Runnable preamble) {
|
||||
if (!(st instanceof ScopeTokenImpl(List<Token> tokens,
|
||||
boolean isTransparentForNames,
|
||||
boolean isTransparentForHashtags,
|
||||
boolean isTransparentForSetFuelCost))) {
|
||||
throw new RuntimeException("Internal error: could not unpack ScopeTokenImpl.");
|
||||
}
|
||||
|
||||
// We need the CodeFrame for local names.
|
||||
CodeFrame outerCodeFrame = currentCodeFrame;
|
||||
if (!isTransparentForNames) {
|
||||
currentCodeFrame = CodeFrame.make(currentCodeFrame, false);
|
||||
}
|
||||
|
||||
// We need to be able to define local hashtag replacements, but still
|
||||
// see the outer ones. We also need to have the same id for dollar
|
||||
// replacement as the outer frame. And we need to be able to allow
|
||||
// local setFuelCost definitions.
|
||||
TemplateFrame innerTemplateFrame = null;
|
||||
if (!isTransparentForHashtags || !isTransparentForSetFuelCost) {
|
||||
innerTemplateFrame = TemplateFrame.makeInnerScope(currentTemplateFrame,
|
||||
isTransparentForHashtags,
|
||||
isTransparentForSetFuelCost);
|
||||
currentTemplateFrame = innerTemplateFrame;
|
||||
}
|
||||
|
||||
// Allow definition of hashtags and variables to be placed in the nested frames.
|
||||
preamble.run();
|
||||
|
||||
// Now render the nested code.
|
||||
renderTokenList(tokens);
|
||||
|
||||
if (!isTransparentForHashtags || !isTransparentForSetFuelCost) {
|
||||
if (currentTemplateFrame != innerTemplateFrame) {
|
||||
throw new RuntimeException("Internal error: TemplateFrame mismatch!");
|
||||
}
|
||||
currentTemplateFrame = currentTemplateFrame.parent;
|
||||
}
|
||||
|
||||
// Tear down CodeFrame nesting. If no nesting happened, the code is already
|
||||
// in the currentCodeFrame.
|
||||
if (!isTransparentForNames) {
|
||||
outerCodeFrame.addCode(currentCodeFrame.getCode());
|
||||
currentCodeFrame = outerCodeFrame;
|
||||
}
|
||||
}
|
||||
|
||||
private void renderToken(Token token) {
|
||||
switch (token) {
|
||||
case StringToken(String s) -> {
|
||||
renderStringWithDollarAndHashtagReplacements(s);
|
||||
}
|
||||
case NothingToken() -> {
|
||||
// Nothing.
|
||||
}
|
||||
case HookAnchorToken(Hook hook, List<Token> tokens) -> {
|
||||
case HookAnchorToken(Hook hook, ScopeTokenImpl innerScope) -> {
|
||||
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);
|
||||
// We need a CodeFrame to which the hook can insert code. If the nested names
|
||||
// are to be local, the CodeFrame must be non-transparent for names.
|
||||
CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame, innerScope.isTransparentForNames());
|
||||
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);
|
||||
// We need a CodeFrame where the tokens can be rendered for code that is
|
||||
// generated inside the anchor scope, but not inserted directly to the hook.
|
||||
CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame, innerScope.isTransparentForNames());
|
||||
currentCodeFrame = innerCodeFrame;
|
||||
|
||||
renderTokenList(tokens);
|
||||
renderScopeToken(innerScope);
|
||||
|
||||
// Close the hookCodeFrame and innerCodeFrame. hookCodeFrame code comes before the
|
||||
// innerCodeFrame code from the tokens.
|
||||
@ -286,20 +317,20 @@ final class Renderer {
|
||||
currentCodeFrame.addCode(hookCodeFrame.getCode());
|
||||
currentCodeFrame.addCode(innerCodeFrame.getCode());
|
||||
}
|
||||
case HookInsertToken(Hook hook, TemplateToken templateToken) -> {
|
||||
case HookInsertToken(Hook hook, ScopeTokenImpl scopeToken) -> {
|
||||
// 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
|
||||
// by the scopeToken can be collected, and hook insertions from it can still
|
||||
// be made to the hookCodeFrame before the code from the scopeToken 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);
|
||||
// the hookCodeFrame, and are not limited to the CodeFrame for the scopeToken.
|
||||
currentCodeFrame = CodeFrame.make(hookCodeFrame, true);
|
||||
|
||||
renderTemplateToken(templateToken);
|
||||
renderScopeToken(scopeToken);
|
||||
|
||||
hookCodeFrame.addCode(currentCodeFrame.getCode());
|
||||
|
||||
@ -307,18 +338,68 @@ final class Renderer {
|
||||
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);
|
||||
}
|
||||
case ScopeToken scopeToken -> {
|
||||
renderScopeToken(scopeToken);
|
||||
}
|
||||
case NameSampleToken nameScopeToken -> {
|
||||
Name name = currentCodeFrame.sampleName(nameScopeToken.predicate());
|
||||
if (name == null) {
|
||||
throw new RendererException("No Name found for " + nameScopeToken.predicate().toString());
|
||||
}
|
||||
ScopeToken scopeToken = nameScopeToken.getScopeToken(name);
|
||||
renderScopeToken(scopeToken, () -> {
|
||||
if (nameScopeToken.name() != null) {
|
||||
addHashtagReplacement(nameScopeToken.name(), name.name());
|
||||
}
|
||||
if (nameScopeToken.type() != null) {
|
||||
addHashtagReplacement(nameScopeToken.type(), name.type());
|
||||
}
|
||||
});
|
||||
}
|
||||
case NameForEachToken nameForEachToken -> {
|
||||
List<Name> list = currentCodeFrame.listNames(nameForEachToken.predicate());
|
||||
list.stream().forEach(name -> {
|
||||
ScopeToken scopeToken = nameForEachToken.getScopeToken(name);
|
||||
renderScopeToken(scopeToken, () -> {
|
||||
if (nameForEachToken.name() != null) {
|
||||
addHashtagReplacement(nameForEachToken.name(), name.name());
|
||||
}
|
||||
if (nameForEachToken.type() != null) {
|
||||
addHashtagReplacement(nameForEachToken.type(), name.type());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
case NamesToListToken nameToListToken -> {
|
||||
List<Name> list = currentCodeFrame.listNames(nameToListToken.predicate());
|
||||
renderScopeToken(nameToListToken.getScopeToken(list));
|
||||
}
|
||||
case NameCountToken nameCountToken -> {
|
||||
int count = currentCodeFrame.countNames(nameCountToken.predicate());
|
||||
renderScopeToken(nameCountToken.getScopeToken(count));
|
||||
}
|
||||
case NameHasAnyToken nameHasAnyToken -> {
|
||||
boolean hasAny = currentCodeFrame.hasAnyNames(nameHasAnyToken.predicate());
|
||||
renderScopeToken(nameHasAnyToken.getScopeToken(hasAny));
|
||||
}
|
||||
case SetFuelCostToken(float fuelCost) -> {
|
||||
currentTemplateFrame.setFuelCost(fuelCost);
|
||||
}
|
||||
case LetToken letToken -> {
|
||||
ScopeToken scopeToken = letToken.getScopeToken();
|
||||
renderScopeToken(scopeToken, () -> {
|
||||
addHashtagReplacement(letToken.key(), letToken.value());
|
||||
});
|
||||
}
|
||||
case HookIsAnchoredToken hookIsAnchoredToken -> {
|
||||
boolean isAnchored = currentCodeFrame.codeFrameForHook(hookIsAnchoredToken.hook()) != null;
|
||||
renderScopeToken(hookIsAnchoredToken.getScopeToken(isAnchored));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,10 +504,6 @@ final class Renderer {
|
||||
));
|
||||
}
|
||||
|
||||
boolean isAnchored(Hook hook) {
|
||||
return currentCodeFrame.codeFrameForHook(hook) != null;
|
||||
}
|
||||
|
||||
private CodeFrame codeFrameForHook(Hook hook) {
|
||||
CodeFrame codeFrame = currentCodeFrame.codeFrameForHook(hook);
|
||||
if (codeFrame == null) {
|
||||
|
||||
@ -23,12 +23,8 @@
|
||||
|
||||
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.
|
||||
* A {@link ScopeToken} represents a scope in a {@link Template}, which can be
|
||||
* created with {@link Template#scope}, {@link Template#transparentScope}, and other related methods.
|
||||
*/
|
||||
public record TemplateBody(List<Token> tokens) {}
|
||||
public sealed interface ScopeToken extends Token permits ScopeTokenImpl {}
|
||||
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Represents a scope with its tokens. Boolean flags indicate if names,
|
||||
* hashtag replacements and {@link Template#setFuelCost} are local, or escape to
|
||||
* outer scopes.
|
||||
*
|
||||
* <p>
|
||||
* Note: We want the {@link ScopeToken} to be public, but the internals of the
|
||||
* record should be private. One way to solve this is with a public interface
|
||||
* that exposes nothing but its name, and a private implementation via a
|
||||
* record that allows easy destructuring with pattern matching.
|
||||
*/
|
||||
record ScopeTokenImpl(List<Token> tokens,
|
||||
boolean isTransparentForNames,
|
||||
boolean isTransparentForHashtags,
|
||||
boolean isTransparentForSetFuelCost) implements ScopeToken, Token {}
|
||||
@ -23,4 +23,7 @@
|
||||
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
record NothingToken() implements Token {}
|
||||
/**
|
||||
* Represents the setting of the fuel cost in the current scope.
|
||||
*/
|
||||
record SetFuelCostToken(float fuelCost) implements Token {}
|
||||
@ -24,6 +24,7 @@
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link StructuralName}s represent things like method and class names, and can be added to the local
|
||||
@ -89,16 +90,34 @@ public record StructuralName(String name, StructuralName.Type type, int weight)
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
// Wrap the FilteredSet as a Predicate.
|
||||
private record StructuralNamePredicate(FilteredSet fs) implements NameSet.Predicate {
|
||||
public boolean check(Name type) {
|
||||
return fs.check(type);
|
||||
}
|
||||
public String toString() {
|
||||
return fs.toString();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
return new StructuralNamePredicate(this);
|
||||
}
|
||||
|
||||
boolean check(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;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")";
|
||||
String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")";
|
||||
return "StructuralName.FilteredSet(" + msg1 + msg2 + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,55 +165,125 @@ public record StructuralName(String name, StructuralName.Type type, int weight)
|
||||
|
||||
/**
|
||||
* Samples a random {@link StructuralName} from the filtered set, according to the weights
|
||||
* of the contained {@link StructuralName}s.
|
||||
* of the contained {@link StructuralName}s, making the sampled {@link StructuralName}
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @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.
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the sampled {@link StructuralName}.
|
||||
* @return a token that represents the sampling and inner scope.
|
||||
* @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());
|
||||
public Token sample(Function<StructuralName, ScopeToken> function) {
|
||||
return new NameSampleToken<>(predicate(), null, null, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any {@link StructuralName}s in the filtered set.
|
||||
* Samples a random {@link StructuralName} from the filtered set, according to the weights
|
||||
* of the contained {@link StructuralName}s, and makes a hashtag replacement for both
|
||||
* the name and type of the {@link StructuralName}, in the current scope.
|
||||
*
|
||||
* @return Returns {@code true} iff there is at least one {@link StructuralName} in the filtered set.
|
||||
* @param name the key of the hashtag replacement for the {@link StructuralName} name.
|
||||
* @param type the key of the hashtag replacement for the {@link StructuralName} type.
|
||||
* @return a token that represents the sampling and hashtag replacement definition.
|
||||
* @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());
|
||||
public Token sampleAndLetAs(String name, String type) {
|
||||
return new NameSampleToken<StructuralName>(predicate(), name, type, n -> Template.transparentScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all {@link StructuralName}s in the filtered set.
|
||||
* Samples a random {@link StructuralName} from the filtered set, according to the weights
|
||||
* of the contained {@link StructuralName}s, and makes a hashtag replacement for the
|
||||
* name of the {@link StructuralName}, in the current scope.
|
||||
*
|
||||
* @param name the key of the hashtag replacement for the {@link StructuralName} name.
|
||||
* @return a token that represents the sampling and hashtag replacement definition.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token sampleAndLetAs(String name) {
|
||||
return new NameSampleToken<StructuralName>(predicate(), name, null, n -> Template.transparentScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of {@link StructuralName}s in the filtered set, making the count
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the count.
|
||||
* @return a token that represents the counting and inner scope.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token count(Function<Integer, ScopeToken> function) {
|
||||
return new NameCountToken(predicate(), function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any {@link StructuralName}s in the filtered set, making the resulting boolean
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the boolean indicating iff there are any {@link StructuralName}s in the filtered set.
|
||||
* @return a token that represents the checking and inner scope.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token hasAny(Function<Boolean, ScopeToken> function) {
|
||||
return new NameHasAnyToken(predicate(), function);
|
||||
}
|
||||
/**
|
||||
* Collects all {@link StructuralName}s in the filtered set, making the collected list
|
||||
* available to an inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that creates the inner {@link ScopeToken} given
|
||||
* the list of {@link StructuralName}.
|
||||
* @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();
|
||||
public Token toList(Function<List<StructuralName>, ScopeToken> function) {
|
||||
return new NamesToListToken<>(predicate(), function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided {@code function} for each {@link StructuralName}s in the filtered set,
|
||||
* making each of these {@link StructuralName}s available to a separate inner scope.
|
||||
*
|
||||
* @param function The {@link Function} that is called to create the inner {@link ScopeToken}s
|
||||
* for each of the {@link StructuralName}s in the filtereds set.
|
||||
* @return The token representing the for-each execution and the respective inner scopes.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token forEach(Function<StructuralName, ScopeToken> function) {
|
||||
return new NameForEachToken<>(predicate(), null, null, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided {@code function} for each {@link StructuralName}s in the filtered set,
|
||||
* making each of these {@link StructuralName}s available to a separate inner scope, and additionally
|
||||
* setting hashtag replacements for the {@code name} and {@code type} of the respective
|
||||
* {@link StructuralName}s.
|
||||
*
|
||||
* <p>
|
||||
* Note, to avoid duplication of the {@code name} and {@code type}
|
||||
* hashtag replacements, the scope created by the provided {@code function} should be
|
||||
* non-transparent to hashtag replacements, for example {@link Template#scope} or
|
||||
* {@link Template#hashtagScope}.
|
||||
*
|
||||
* @param name the key of the hashtag replacement for each individual {@link StructuralName} name.
|
||||
* @param type the key of the hashtag replacement for each individual {@link StructuralName} type.
|
||||
* @param function The {@link Function} that is called to create the inner {@link ScopeToken}s
|
||||
* for each of the {@link StructuralName}s in the filtereds set.
|
||||
* @return The token representing the for-each execution and the respective inner scopes.
|
||||
* @throws UnsupportedOperationException If the type was not constrained with either of
|
||||
* {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}.
|
||||
*/
|
||||
public Token forEach(String name, String type, Function<StructuralName, ScopeToken> function) {
|
||||
return new NameForEachToken<>(predicate(), name, type, function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body(
|
||||
* var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope(
|
||||
* let("con1", generator.next()),
|
||||
* let("con2", generator.next()),
|
||||
* """
|
||||
@ -86,13 +86,13 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* To get an executable test, we define a {@link Template} that produces a class body with a main method. The Template
|
||||
* To get an executable test, we define a {@link Template} that produces a class scope 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(
|
||||
* var classTemplate = Template.make("types", (List<Type> types) -> scope(
|
||||
* let("classpath", comp.getEscapedClassPathOfCompiledClasses()),
|
||||
* """
|
||||
* package p.xyz;
|
||||
@ -148,12 +148,12 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* {@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.
|
||||
* 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}.
|
||||
* (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#scope}.
|
||||
*
|
||||
* <p>
|
||||
* Ideally, we would have used <a href="https://openjdk.org/jeps/430">string templates</a> to inject these Template
|
||||
@ -161,6 +161,11 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* <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}.
|
||||
* We have decided to keep hashtag replacements constrained to the scope of one Template. They
|
||||
* do not escape to outer or inner Template uses. If one needs to pass values to inner Templates,
|
||||
* this can be done with Template arguments. Keeping hashtag replacements local to Templates
|
||||
* has the benefit that there is no conflict in recursive templates, where outer and inner Templates
|
||||
* define the same hashtag replacement.
|
||||
*
|
||||
* <p>
|
||||
* When using nested Templates, there can be collisions with identifiers (e.g. variable names and method names).
|
||||
@ -176,25 +181,6 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* {@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
|
||||
@ -211,61 +197,70 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* 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.
|
||||
* Code generation can involve keeping track of scopes in the code (e.g. liveness and availability of
|
||||
* {@link DataName}s) and of the hashtag replacements in the templates. The {@link ScopeToken} serves
|
||||
* this purpose, and allows the definition of transparent scopes (e.g. {@link #transparentScope}) and
|
||||
* non-transparent scopes (e.g. {@link #scope}).
|
||||
*
|
||||
* <table border="1">
|
||||
* <caption>Scopes and (non-)transparency</caption>
|
||||
* <tr>
|
||||
* <th> </th><th> hashtag </th><th> {@link DataName} and {@link StructuralName} </th><th> {@link #setFuelCost} </th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <th> {@link #scope} </th><th> non-transparent </th><th> non-transparent </th><th> non-transparent </th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <th> {@link #hashtagScope} </th><th> non-transparent </th><th> transparent </th><th> transparent </th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <th> {@link #nameScope} </th><th> transparent </th><th> non-transparent </th><th> transparent </th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <th> {@link #setFuelCostScope} </th><th> transparent </th><th> transparent </th><th> non-transparent </th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <th> {@link #transparentScope} </th><th> transparent </th><th> transparent </th><th> transparent </th>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <p>
|
||||
* Let us look at the following example to better understand the execution order.
|
||||
* In some cases, we may be deeper nested in templates and scopes, and would like to reach "back" or
|
||||
* to outer scopes. This is possible with {@link Hook#anchor}ing in some outer scope, and later
|
||||
* {@link Hook#insert}ing from an inner scope to the scope of the anchoring. 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>
|
||||
* {@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.
|
||||
* ));
|
||||
* }
|
||||
|
||||
* 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 #scope(Object...)}. Recursive templates are
|
||||
* supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero).
|
||||
*
|
||||
* <p>
|
||||
* A note from the implementor to the user: We have decided to implement the Template Framework using
|
||||
* a functional (lambdas) and data-oriented (tokens) model. The consequence is that there are three
|
||||
* orders in template rendering: (1) the execution order in lambdas, where we usually assemble the
|
||||
* tokens and pass them to some scope ({@link ScopeToken}) as arguments. (2) the token evaluation
|
||||
* order, which occurs in the order of how tokens are listed in a scope. By design, the token order
|
||||
* is the same order as execution in lambdas. To keep the lambda and token order in sync, most of the
|
||||
* queries about the state of code generation, such as {@link DataName}s and {@link Hook}s cannot
|
||||
* return the values immediately, but have to be expressed as tokens. If we had a mix of tokens and
|
||||
* immediate queries, then the immediate queries would "float" by the tokens, because the immediate
|
||||
* queries are executed during the lambda execution, but the tokens are only executed later. Having
|
||||
* to express everything as tokens can be a little more cumbersome (e.g. sample requires a lambda
|
||||
* that captures the {@link DataName}, and sample does not return the {@link DataName} directly).
|
||||
* But this ensures that reasoning about execution order is relatively straight forward, namely in
|
||||
* the order of the specified tokens. (3) the final code order is the same as the lambda and token
|
||||
* order, except when using {@link Hook#insert}, which places the code at the innermost {@link Hook#anchor}.
|
||||
*
|
||||
* <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
|
||||
@ -281,10 +276,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
/**
|
||||
* A {@link Template} with no arguments.
|
||||
*
|
||||
* @param function The {@link Supplier} that creates the {@link TemplateBody}.
|
||||
* @param function The {@link Supplier} that creates the {@link ScopeToken}.
|
||||
*/
|
||||
record ZeroArgs(Supplier<TemplateBody> function) implements Template {
|
||||
TemplateBody instantiate() {
|
||||
record ZeroArgs(Supplier<ScopeToken> function) implements Template {
|
||||
ScopeToken instantiate() {
|
||||
return function.get();
|
||||
}
|
||||
|
||||
@ -324,10 +319,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
*
|
||||
* @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.
|
||||
* @param function The {@link Function} that creates the {@link ScopeToken} given the template argument.
|
||||
*/
|
||||
record OneArg<T1>(String arg1Name, Function<T1, TemplateBody> function) implements Template {
|
||||
TemplateBody instantiate(T1 arg1) {
|
||||
record OneArg<T1>(String arg1Name, Function<T1, ScopeToken> function) implements Template {
|
||||
ScopeToken instantiate(T1 arg1) {
|
||||
return function.apply(arg1);
|
||||
}
|
||||
|
||||
@ -372,10 +367,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* @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.
|
||||
* @param function The {@link BiFunction} that creates the {@link ScopeToken} 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) {
|
||||
record TwoArgs<T1, T2>(String arg1Name, String arg2Name, BiFunction<T1, T2, ScopeToken> function) implements Template {
|
||||
ScopeToken instantiate(T1 arg1, T2 arg2) {
|
||||
return function.apply(arg1, arg2);
|
||||
}
|
||||
|
||||
@ -447,10 +442,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* @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.
|
||||
* @param function The function with three arguments that creates the {@link ScopeToken} 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) {
|
||||
record ThreeArgs<T1, T2, T3>(String arg1Name, String arg2Name, String arg3Name, TriFunction<T1, T2, T3, ScopeToken> function) implements Template {
|
||||
ScopeToken instantiate(T1 arg1, T2 arg2, T3 arg3) {
|
||||
return function.apply(arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@ -496,28 +491,28 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with no arguments.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* See {@link #scope} for more details about how to construct a Template with {@link Token}s.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> body(
|
||||
* var template = Template.make(() -> scope(
|
||||
* """
|
||||
* Multi-line string or other tokens.
|
||||
* """
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @param scope The {@link ScopeToken} created by {@link Template#scope}.
|
||||
* @return A {@link Template} with zero arguments.
|
||||
*/
|
||||
static Template.ZeroArgs make(Supplier<TemplateBody> body) {
|
||||
return new Template.ZeroArgs(body);
|
||||
static Template.ZeroArgs make(Supplier<ScopeToken> scope) {
|
||||
return new Template.ZeroArgs(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with one argument.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* See {@link #scope} 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>
|
||||
@ -525,7 +520,7 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* 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(
|
||||
* var template = Template.make("a", (Integer a) -> scope(
|
||||
* """
|
||||
* Multi-line string or other tokens.
|
||||
* We can use the hashtag replacement #a to directly insert the String value of a.
|
||||
@ -534,18 +529,18 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @param scope The {@link ScopeToken} created by {@link Template#scope}.
|
||||
* @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);
|
||||
static <T1> Template.OneArg<T1> make(String arg1Name, Function<T1, ScopeToken> scope) {
|
||||
return new Template.OneArg<>(arg1Name, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with two arguments.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* See {@link #scope} 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>
|
||||
@ -553,7 +548,7 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* 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(
|
||||
* var template = Template.make("a", "b", (Integer a, String b) -> scope(
|
||||
* """
|
||||
* 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.
|
||||
@ -562,23 +557,23 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @param body The {@link TemplateBody} created by {@link Template#body}.
|
||||
* @param scope The {@link ScopeToken} created by {@link Template#scope}.
|
||||
* @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);
|
||||
static <T1, T2> Template.TwoArgs<T1, T2> make(String arg1Name, String arg2Name, BiFunction<T1, T2, ScopeToken> scope) {
|
||||
return new Template.TwoArgs<>(arg1Name, arg2Name, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Template} with three arguments.
|
||||
* See {@link #body} for more details about how to construct a Template with {@link Token}s.
|
||||
* See {@link #scope} 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 scope The {@link ScopeToken} created by {@link Template#scope}.
|
||||
* @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.
|
||||
@ -587,18 +582,35 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* @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);
|
||||
static <T1, T2, T3> Template.ThreeArgs<T1, T2, T3> make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction<T1, T2, T3, ScopeToken> scope) {
|
||||
return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Creates a {@link ScopeToken} that represents a scope that is completely
|
||||
* non-transparent, <strong>not</strong> allowing anything to escape. This
|
||||
* means that no {@link DataName}, {@link StructuralName}s, hashtag-replacement
|
||||
* or {@link #setFuelCost} defined inside the scope is available outside. All
|
||||
* these usages are only local to the defining scope here.
|
||||
*
|
||||
* <p>
|
||||
* The scope is formed 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>
|
||||
* If you require a scope that is either fully transparent (i.e. everything escapes)
|
||||
* or only restricts a specific kind to not escape, consider using one of the other
|
||||
* provided scopes: {@link #transparentScope}, {@link #nameScope}, {@link #hashtagScope},
|
||||
* or {@link #setFuelCostScope}. A "scope-transparency-matrix" can also be found in
|
||||
* the interface comment for {@link Template}.
|
||||
*
|
||||
* <p>
|
||||
* The most common use of {@link #scope} is in the construction of templates:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> body(
|
||||
* var template = Template.make(() -> scope(
|
||||
* """
|
||||
* Multi-line string
|
||||
* """,
|
||||
@ -608,14 +620,200 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* Note that regardless of the chosen scope for {@code Template.make},
|
||||
* hashtag-replacements and {@link #setFuelCost} are always implicitly
|
||||
* non-transparent (i.e. non-escaping). For example, {@link #let} will
|
||||
* not escape the template scope even when using {@link #transparentScope}.
|
||||
* As a default, it is recommended to use {@link #scope} for
|
||||
* {@code Template.make} since in most cases template scopes align with
|
||||
* code scopes that are non-transparent for fields, variables, etc. In
|
||||
* rare cases, where the scope of the template needs to be transparent
|
||||
* (e.g. because we need to insert a variable or field into an outer scope),
|
||||
* it is recommended to use {@link #transparentScope}. This allows to make
|
||||
* {@link DataName}s and {@link StructuralName}s available outside this
|
||||
* template crossing the template boundary.
|
||||
*
|
||||
* <p>
|
||||
* We can also use nested scopes inside of templates:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* // CODE1: some code in the outer scope
|
||||
* scope(
|
||||
* // CODE2: some code in the inner scope. Names, hashtags and setFuelCost
|
||||
* // do not escape the inner scope.
|
||||
* ),
|
||||
* // CODE3: more code in the outer scope, names and hashtags from CODE2 are
|
||||
* // not available anymore because of the non-transparent "scope".
|
||||
* transparentScope(
|
||||
* // CODE4: some code in the inner "transparentScope". Names, hashtags and setFuelCost
|
||||
* // escape the "transparentScope" and are still available after the "transparentScope"
|
||||
* // closes.
|
||||
* )
|
||||
* // CODE5: we still have access to names and hashtags from CODE4.
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @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.
|
||||
* @return The {@link ScopeToken} 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(TokenParser.parse(tokens));
|
||||
static ScopeToken scope(Object... tokens) {
|
||||
return new ScopeTokenImpl(TokenParser.parse(tokens), false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ScopeToken} that represents a completely transparent scope.
|
||||
* This means that {@link DataName}s, {@link StructuralName}s,
|
||||
* hashtag-replacements and {@link #setFuelCost} declared inside the scope will be available
|
||||
* in the outer scope.
|
||||
* The scope is formed 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>
|
||||
* If you require a scope that is non-transparent (i.e. nothing escapes) or only restricts
|
||||
* a specific kind to not escape, consider using one of the other provided scopes:
|
||||
* {@link #scope}, {@link #nameScope}, {@link #hashtagScope}, or {@link #setFuelCostScope}.
|
||||
* A "scope-transparency-matrix" can also be found in the interface comment for {@link Template}.
|
||||
*
|
||||
* @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 ScopeToken} which captures the list of validated {@link Token}s.
|
||||
* @throws IllegalArgumentException if the list of tokens contains an unexpected object.
|
||||
*/
|
||||
static ScopeToken transparentScope(Object... tokens) {
|
||||
return new ScopeTokenImpl(TokenParser.parse(tokens), true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ScopeToken} that represents a scope that is non-transparent for
|
||||
* {@link DataName}s and {@link StructuralName}s (i.e. cannot escape), but
|
||||
* transparent for hashtag-replacements and {@link #setFuelCost} (i.e. available
|
||||
* in outer scope).
|
||||
*
|
||||
* <p>
|
||||
* The scope is formed 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>
|
||||
* If you require a scope that is transparent or uses a different restriction, consider
|
||||
* using one of the other provided scopes: {@link #scope}, {@link #transparentScope},
|
||||
* {@link #hashtagScope}, or {@link #setFuelCostScope}. A "scope-transparency-matrix" can
|
||||
* also be found in the interface comment for {@link Template}.
|
||||
*
|
||||
* @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 ScopeToken} which captures the list of validated {@link Token}s.
|
||||
* @throws IllegalArgumentException if the list of tokens contains an unexpected object.
|
||||
*/
|
||||
static ScopeToken nameScope(Object... tokens) {
|
||||
return new ScopeTokenImpl(TokenParser.parse(tokens), false, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ScopeToken} that represents a scope that is non-transparent for
|
||||
* hashtag-replacements (i.e. cannot escape), but transparent for {@link DataName}s
|
||||
* and {@link StructuralName}s and {@link #setFuelCost} (i.e. available in outer scope).
|
||||
*
|
||||
* <p>
|
||||
* The scope is formed 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>
|
||||
* If you require a scope that is transparent or uses a different restriction, consider
|
||||
* using one of the other provided scopes: {@link #scope}, {@link #transparentScope},
|
||||
* {@link #nameScope}, or {@link #setFuelCostScope}. A "scope-transparency-matrix" can
|
||||
* also be found in the interface comment for {@link Template}.
|
||||
*
|
||||
* <p>
|
||||
* Keeping hashtag-replacements local but letting {@link DataName}s escape can be
|
||||
* useful in cases like the following, where we may want to reuse the hashtag
|
||||
* multiple times:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* List.of("a", "b", "c").stream().map(name -> hashtagScope(
|
||||
* let("name", name), // assumes values: a, b, c
|
||||
* addDataName(name, PrimitiveType.INTS, MUTABLE), // escapes
|
||||
* """
|
||||
* int #name = 42;
|
||||
* """
|
||||
* ))
|
||||
* // We still have access to the three DataNames.
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @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 ScopeToken} which captures the list of validated {@link Token}s.
|
||||
* @throws IllegalArgumentException if the list of tokens contains an unexpected object.
|
||||
*/
|
||||
static ScopeToken hashtagScope(Object... tokens) {
|
||||
return new ScopeTokenImpl(TokenParser.parse(tokens), true, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ScopeToken} that represents a scope that is non-transparent for
|
||||
* {@link #setFuelCost} (i.e. cannot escape), but transparent for hashtag-replacements,
|
||||
* {@link DataName}s and {@link StructuralName}s (i.e. available in outer scope).
|
||||
* The scope is formed 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>
|
||||
* If you require a scope that is transparent or uses a different restriction, consider
|
||||
* using one of the other provided scopes: {@link #scope}, {@link #transparentScope},
|
||||
* {@link #hashtagScope}, or {@link #nameScope}. A "scope-transparency-matrix" can
|
||||
* also be found in the interface comment for {@link Template}.
|
||||
*
|
||||
* <p>
|
||||
* In some cases, it can be helpful to have different {@link #setFuelCost} within
|
||||
* a single template, depending on the code nesting depth. Example:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make(() -> scope(
|
||||
* setFuelCost(1),
|
||||
* // CODE1: some shallow code, allowing recursive template uses here
|
||||
* // to use more fuel.
|
||||
* """
|
||||
* for (int i = 0; i < 1000; i++) {
|
||||
* """,
|
||||
* setFuelCostScope(
|
||||
* setFuelCost(100)
|
||||
* // CODE2: with the for-loop, we already have a deeper nesting
|
||||
* // depth, and recursive template uses should not get
|
||||
* // as much fuel as in CODE1.
|
||||
* ),
|
||||
* """
|
||||
* }
|
||||
* """
|
||||
* // CODE3: we are back in the outer scope of CODE1, and can use
|
||||
* // more fuel again in nested template uses. setFuelCost
|
||||
* // is automatically restored to what was set before the
|
||||
* // inner scope.
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* @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 ScopeToken} which captures the list of validated {@link Token}s.
|
||||
* @throws IllegalArgumentException if the list of tokens contains an unexpected object.
|
||||
*/
|
||||
static ScopeToken setFuelCostScope(Object... tokens) {
|
||||
return new ScopeTokenImpl(TokenParser.parse(tokens), true, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -628,7 +826,7 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* 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(
|
||||
* var template = Template.make(() -> scope(
|
||||
* """
|
||||
* int $var = 42;
|
||||
* """,
|
||||
@ -640,6 +838,9 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* @return The dollar replacement for the {@code 'name'}.
|
||||
*/
|
||||
static String $(String name) {
|
||||
// Note, since the dollar replacements do not change within a template
|
||||
// and the retrieval has no side effects, we can return the value immediately,
|
||||
// and do not need a token.
|
||||
return Renderer.getCurrent().$(name);
|
||||
}
|
||||
|
||||
@ -648,7 +849,7 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
*
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var template = Template.make("a", (Integer a) -> body(
|
||||
* var template = Template.make("a", (Integer a) -> scope(
|
||||
* let("b", a * 5),
|
||||
* """
|
||||
* System.out.println("Use a and b with hashtag replacement: #a and #b");
|
||||
@ -656,41 +857,50 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* ));
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* Note that a {@code let} definition makes the hashtag replacement available
|
||||
* for anything that follows it, until the the end of the next outer scope
|
||||
* that is non-transparent for hashtag replacements. Additionally, hashtag
|
||||
* replacements are limited to the template they were defined in.
|
||||
* If you want to pass values from an outer to an inner template, this cannot
|
||||
* be done with hashtags directly. Instead, one has to pass the values via
|
||||
* template arguments.
|
||||
*
|
||||
* @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}.
|
||||
* @return A token that represents the hashtag replacement definition.
|
||||
*/
|
||||
static Token let(String key, Object value) {
|
||||
Renderer.getCurrent().addHashtagReplacement(key, value);
|
||||
return new NothingToken();
|
||||
return new LetToken(key, value, v -> transparentScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a hashtag replacement for {@code "#key"}, with a specific value, which is also captured
|
||||
* by the provided {@code function} with type {@code <T>}.
|
||||
* by the provided {@code function} with type {@code <T>}. While the argument of the lambda that
|
||||
* captures the value is naturally bounded to the scope of the lambda, the hashtag replacement
|
||||
* may be bound to the scope or escape it, depending on the choice of scope, see {@link #scope}
|
||||
* and {@link #transparentScope}.
|
||||
*
|
||||
* <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"
|
||||
* )));
|
||||
* var template = Template.make("a", (Integer a) -> scope(
|
||||
* let("b", a * 2, (Integer b) -> scope(
|
||||
* """
|
||||
* 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}.
|
||||
* @return A {@link Token} representing the hashtag replacement definition and inner scope.
|
||||
*/
|
||||
static <T> TemplateBody let(String key, T value, Function<T, TemplateBody> function) {
|
||||
Renderer.getCurrent().addHashtagReplacement(key, value);
|
||||
return function.apply(value);
|
||||
static <T> Token let(String key, T value, Function<T, ScopeToken> function) {
|
||||
return new LetToken(key, value, function);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -702,7 +912,7 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
/**
|
||||
* 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...)}.
|
||||
* with {@link #setFuelCost(float)} inside {@link #scope(Object...)}.
|
||||
*/
|
||||
float DEFAULT_FUEL_COST = 10.0f;
|
||||
|
||||
@ -721,7 +931,7 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* <p>
|
||||
* {@snippet lang=java :
|
||||
* var binding = new TemplateBinding<Template.OneArg<Integer>>();
|
||||
* var template = Template.make("depth", (Integer depth) -> body(
|
||||
* var template = Template.make("depth", (Integer depth) -> scope(
|
||||
* setFuelCost(5.0f),
|
||||
* let("fuel", fuel()),
|
||||
* """
|
||||
@ -737,6 +947,9 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* @return The amount of fuel left for nested Template use.
|
||||
*/
|
||||
static float fuel() {
|
||||
// Note, since the fuel amount does not change within a template
|
||||
// and the retrieval has no side effects, we can return the value immediately,
|
||||
// and do not need a token.
|
||||
return Renderer.getCurrent().fuel();
|
||||
}
|
||||
|
||||
@ -745,16 +958,17 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
* {@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}.
|
||||
* @return A token for convenient use in {@link Template#scope}.
|
||||
*/
|
||||
static Token setFuelCost(float fuelCost) {
|
||||
Renderer.getCurrent().setFuelCost(fuelCost);
|
||||
return new NothingToken();
|
||||
return new SetFuelCostToken(fuelCost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link DataName} in the current scope, that is the innermost of either
|
||||
* {@link Template#body} or {@link Hook#anchor}.
|
||||
* Add a {@link DataName} in the current {@link #scope}.
|
||||
* If the current scope is transparent to {@link DataName}s, it escapes to the next
|
||||
* outer scope that is non-transparent, and is available for everything that follows
|
||||
* the {@code addDataName} until the end of that non-transparent scope.
|
||||
*
|
||||
* @param name The name of the {@link DataName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link DataName}.
|
||||
@ -779,8 +993,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Add a {@link DataName} in the current {@link #scope}, with a {@code weight} of 1.
|
||||
* If the current scope is transparent to {@link DataName}s, it escapes to the next
|
||||
* outer scope that is non-transparent, and is available for everything that follows
|
||||
* the {@code addDataName} until the end of that non-transparent scope.
|
||||
*
|
||||
* @param name The name of the {@link DataName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link DataName}.
|
||||
@ -804,8 +1020,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link StructuralName} in the current scope, that is the innermost of either
|
||||
* {@link Template#body} or {@link Hook#anchor}.
|
||||
* Add a {@link StructuralName} in the current {@link #scope}.
|
||||
* If the current scope is transparent to {@link StructuralName}s, it escapes to the next
|
||||
* outer scope that is non-transparent, and is available for everything that follows
|
||||
* the {@code addStructuralName} until the end of that non-transparent scope.
|
||||
*
|
||||
* @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link StructuralName}.
|
||||
@ -822,8 +1040,10 @@ public sealed interface Template permits Template.ZeroArgs,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Add a {@link StructuralName} in the current {@link #scope}, with a {@code weight} of 1.
|
||||
* If the current scope is transparent to {@link StructuralName}s, it escapes to the next
|
||||
* outer scope that is non-transparent, and is available for everything that follows
|
||||
* the {@code addStructuralName} until the end of that non-transparent scope.
|
||||
*
|
||||
* @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code.
|
||||
* @param type The type of the {@link StructuralName}.
|
||||
|
||||
@ -27,38 +27,96 @@ 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.
|
||||
* The {@link TemplateFrame} keeps track of the nested hashtag replacements available
|
||||
* inside the {@link Template}, as well as the unique id of the {@link Template} use,
|
||||
* and how much fuel is available for recursive {@link Template} calls. The name of
|
||||
* the {@link TemplateFrame} indicates that it corresponds to the structure of the
|
||||
* {@link Template}, whereas the {@link CodeFrame} corresponds to the structure of
|
||||
* the generated code.
|
||||
*
|
||||
* <p>
|
||||
* See also {@link CodeFrame} for more explanations about the frames.
|
||||
* The unique id is used to deconflict names using {@link Template#$}.
|
||||
*
|
||||
* <p>
|
||||
* A {@link Template} can have multiple {@link TemplateFrame}s, if there are nested
|
||||
* scopes. The outermost {@link TemplateFrame} determines the id of the {@link Template}
|
||||
* use and performs the subtraction of fuel from the outer {@link Template}. Inner
|
||||
* {@link TemplateFrame}s ensure the correct availability of hashtag replacement and
|
||||
* {@link Template#setFuelCost} definitions, so that they are local to their scope and
|
||||
* nested scopes, and only escape if the scope is transparent.
|
||||
*
|
||||
* <p>
|
||||
* The hashtag replacements are a set of key-value pairs from the template arguments
|
||||
* and queries such as {@link Template#let} definitions. Each {@link TemplateFrame}
|
||||
* has such a set of hashtag replacements, and implicitly provides access to the
|
||||
* hashtag replacements of the outer {@link TemplateFrame}s, up to the outermost
|
||||
* of the current {@link Template}. If a hashtag replacement is added in a scope,
|
||||
* we have to traverse to outer scopes until we find one that is not transparent
|
||||
* for hashtags (at most it is the frame of the Template), and insert it there.
|
||||
* The hashtag replacent is local to that frame, and accessible for any frames nested
|
||||
* inside it, but not inside other Templates. The hashtag replacement disappears once
|
||||
* the corresponding scope is exited, i.e. the frame removed.
|
||||
*
|
||||
* <p>
|
||||
* The {@link #parent} relationship provides a trace for the use chain of templates and
|
||||
* their inner scopes. The {@link #fuel} is reduced over this chain to give a heuristic
|
||||
* on how deeply nested the code is at a given point, correlating to the runtime that
|
||||
* would be spent if the code was executed. The idea is that once the fuel is depleated,
|
||||
* we do not want to nest more deeply, so that there is a reasonable chance that the
|
||||
* execution of the generated code can terminate.
|
||||
*
|
||||
* <p>
|
||||
* The {@link TemplateFrame} thus implements the hashtag and {@link Template#setFuelCost}
|
||||
* non-transparency aspect of {@link ScopeToken}.
|
||||
*
|
||||
* <p>
|
||||
* See also {@link CodeFrame} for more explanations about the frames. Note, that while
|
||||
* {@link TemplateFrame} always nests inward, even with {@link Hook#insert}, the
|
||||
* {@link CodeFrame} can also jump to the {@link Hook#anchor} {@link CodeFrame} when
|
||||
* using {@link Hook#insert}.
|
||||
*/
|
||||
class TemplateFrame {
|
||||
final TemplateFrame parent;
|
||||
private final boolean isInnerScope;
|
||||
private final int id;
|
||||
private final Map<String, String> hashtagReplacements = new HashMap<>();
|
||||
final float fuel;
|
||||
private float fuelCost;
|
||||
private final boolean isTransparentForHashtag;
|
||||
private final boolean isTransparentForFuel;
|
||||
|
||||
public static TemplateFrame makeBase(int id, float fuel) {
|
||||
return new TemplateFrame(null, id, fuel, 0.0f);
|
||||
return new TemplateFrame(null, false, id, fuel, 0.0f, false, false);
|
||||
}
|
||||
|
||||
public static TemplateFrame make(TemplateFrame parent, int id) {
|
||||
return new TemplateFrame(parent, id, parent.fuel - parent.fuelCost, Template.DEFAULT_FUEL_COST);
|
||||
float fuel = parent.fuel - parent.fuelCost;
|
||||
return new TemplateFrame(parent, false, id, fuel, Template.DEFAULT_FUEL_COST, false, false);
|
||||
}
|
||||
|
||||
private TemplateFrame(TemplateFrame parent, int id, float fuel, float fuelCost) {
|
||||
public static TemplateFrame makeInnerScope(TemplateFrame parent,
|
||||
boolean isTransparentForHashtag,
|
||||
boolean isTransparentForFuel) {
|
||||
// We keep the id of the parent, so that we have the same dollar replacements.
|
||||
// And we subtract no fuel, but forward the cost.
|
||||
return new TemplateFrame(parent, true, parent.id, parent.fuel, parent.fuelCost,
|
||||
isTransparentForHashtag, isTransparentForFuel);
|
||||
}
|
||||
|
||||
private TemplateFrame(TemplateFrame parent,
|
||||
boolean isInnerScope,
|
||||
int id,
|
||||
float fuel,
|
||||
float fuelCost,
|
||||
boolean isTransparentForHashtag,
|
||||
boolean isTransparentForFuel) {
|
||||
this.parent = parent;
|
||||
this.isInnerScope = isInnerScope;
|
||||
this.id = id;
|
||||
this.fuel = fuel;
|
||||
this.fuelCost = fuelCost;
|
||||
this.isTransparentForHashtag = isTransparentForHashtag;
|
||||
this.isTransparentForFuel = isTransparentForFuel;
|
||||
}
|
||||
|
||||
public String $(String name) {
|
||||
@ -78,8 +136,15 @@ class TemplateFrame {
|
||||
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 previous = findHashtagReplacementInScopes(key);
|
||||
if (previous != null) {
|
||||
throw new RendererException("Duplicate hashtag replacement for #" + key + ". " +
|
||||
"previous: " + previous + ", new: " + value);
|
||||
}
|
||||
if (isTransparentForHashtag) {
|
||||
parent.addHashtagReplacement(key, value);
|
||||
} else {
|
||||
hashtagReplacements.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,13 +152,27 @@ class TemplateFrame {
|
||||
if (!Renderer.isValidHashtagOrDollarName(key)) {
|
||||
throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'.");
|
||||
}
|
||||
if (hashtagReplacements.containsKey(key)) {
|
||||
return hashtagReplacements.get(key);
|
||||
String value = findHashtagReplacementInScopes(key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
throw new RendererException("Missing hashtag replacement for #" + key);
|
||||
}
|
||||
|
||||
private String findHashtagReplacementInScopes(String key) {
|
||||
if (hashtagReplacements.containsKey(key)) {
|
||||
return hashtagReplacements.get(key);
|
||||
}
|
||||
if (!isInnerScope) {
|
||||
return null;
|
||||
}
|
||||
return parent.findHashtagReplacementInScopes(key);
|
||||
}
|
||||
|
||||
void setFuelCost(float fuelCost) {
|
||||
this.fuelCost = fuelCost;
|
||||
if (isTransparentForFuel) {
|
||||
parent.setFuelCost(fuelCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ public sealed abstract class TemplateToken implements Token
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
public ScopeToken instantiate() {
|
||||
return zeroArgs.instantiate();
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ public sealed abstract class TemplateToken implements Token
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
public ScopeToken instantiate() {
|
||||
return oneArgs.instantiate(arg1);
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ public sealed abstract class TemplateToken implements Token
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
public ScopeToken instantiate() {
|
||||
return twoArgs.instantiate(arg1, arg2);
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ public sealed abstract class TemplateToken implements Token
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateBody instantiate() {
|
||||
public ScopeToken instantiate() {
|
||||
return threeArgs.instantiate(arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ public sealed abstract class TemplateToken implements Token
|
||||
}
|
||||
}
|
||||
|
||||
abstract TemplateBody instantiate();
|
||||
abstract ScopeToken instantiate();
|
||||
|
||||
@FunctionalInterface
|
||||
interface ArgumentVisitor {
|
||||
|
||||
@ -24,16 +24,25 @@
|
||||
package compiler.lib.template_framework;
|
||||
|
||||
/**
|
||||
* The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either
|
||||
* The {@link Template#scope} 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.
|
||||
*/
|
||||
public sealed interface Token permits StringToken,
|
||||
TemplateToken,
|
||||
TemplateToken.ZeroArgs,
|
||||
TemplateToken.OneArg,
|
||||
TemplateToken.TwoArgs,
|
||||
TemplateToken.ThreeArgs,
|
||||
HookAnchorToken,
|
||||
HookInsertToken,
|
||||
AddNameToken,
|
||||
NothingToken {}
|
||||
TemplateToken,
|
||||
TemplateToken.ZeroArgs,
|
||||
TemplateToken.OneArg,
|
||||
TemplateToken.TwoArgs,
|
||||
TemplateToken.ThreeArgs,
|
||||
HookAnchorToken,
|
||||
HookInsertToken,
|
||||
HookIsAnchoredToken,
|
||||
AddNameToken,
|
||||
NameSampleToken,
|
||||
NameForEachToken,
|
||||
NamesToListToken,
|
||||
NameCountToken,
|
||||
NameHasAnyToken,
|
||||
LetToken,
|
||||
ScopeToken,
|
||||
ScopeTokenImpl,
|
||||
SetFuelCostToken {}
|
||||
|
||||
@ -31,7 +31,7 @@ import java.util.List;
|
||||
* Helper class for {@link Token}, to keep the parsing methods package private.
|
||||
*
|
||||
* <p>
|
||||
* The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either
|
||||
* The {@link Template#scope} 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.
|
||||
|
||||
@ -33,7 +33,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
|
||||
/**
|
||||
* {@link Expression}s model Java expressions, that have a list of arguments with specified
|
||||
@ -357,7 +357,7 @@ public class Expression {
|
||||
}
|
||||
tokens.add(strings.getLast());
|
||||
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
tokens
|
||||
));
|
||||
return template.asToken();
|
||||
|
||||
@ -33,7 +33,7 @@ import compiler.lib.generators.RestrictableGenerator;
|
||||
import compiler.lib.template_framework.DataName;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
|
||||
/**
|
||||
* The {@link PrimitiveType} models Java's primitive types, and provides a set
|
||||
@ -190,7 +190,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType {
|
||||
* @return a TemplateToken that holds all the {@code LibraryRNG} class.
|
||||
*/
|
||||
public static TemplateToken generateLibraryRNG() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
public static class LibraryRNG {
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
|
||||
@ -30,7 +30,7 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
import compiler.lib.compile_framework.CompileFramework;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
|
||||
/**
|
||||
@ -51,7 +51,7 @@ public final class TestFrameworkClass {
|
||||
private TestFrameworkClass() {}
|
||||
|
||||
/**
|
||||
* This method renders a list of {@code testTemplateTokens} into the body of a class
|
||||
* This method renders a list of {@code testTemplateTokens} into the scope of a class
|
||||
* and generates a {@code main} method which launches the {@link TestFramework}
|
||||
* to run the generated tests.
|
||||
*
|
||||
@ -81,7 +81,7 @@ public final class TestFrameworkClass {
|
||||
final Set<String> imports,
|
||||
final String classpath,
|
||||
final List<TemplateToken> testTemplateTokens) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("packageName", packageName),
|
||||
let("className", className),
|
||||
let("classpath", classpath),
|
||||
@ -96,7 +96,7 @@ public final class TestFrameworkClass {
|
||||
public class #className {
|
||||
// --- CLASS_HOOK insertions start ---
|
||||
""",
|
||||
Hooks.CLASS_HOOK.anchor(
|
||||
Hooks.CLASS_HOOK.anchor(scope(
|
||||
"""
|
||||
// --- CLASS_HOOK insertions end ---
|
||||
public static void main(String[] vmFlags) {
|
||||
@ -108,7 +108,7 @@ public final class TestFrameworkClass {
|
||||
// --- LIST OF TESTS start ---
|
||||
""",
|
||||
testTemplateTokens
|
||||
),
|
||||
)),
|
||||
"""
|
||||
// --- LIST OF TESTS end ---
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.generators.Generators;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import static compiler.lib.template_framework.Template.$;
|
||||
|
||||
@ -333,7 +333,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
public TemplateToken index(String invar0, String[] invarRest) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("con", con),
|
||||
let("ivScale", ivScale),
|
||||
let("invar0Scale", invar0Scale),
|
||||
@ -349,7 +349,7 @@ public class TestAliasingFuzzer {
|
||||
// MemorySegment need to be long-addressed, otherwise there can be int-overflow
|
||||
// in the index, and that prevents RangeCheck Elimination and Vectorization.
|
||||
public TemplateToken indexLong(String invar0, String[] invarRest) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("con", con),
|
||||
let("ivScale", ivScale),
|
||||
let("invar0Scale", invar0Scale),
|
||||
@ -365,7 +365,7 @@ public class TestAliasingFuzzer {
|
||||
|
||||
// Mirror the IndexForm from the generator to the test.
|
||||
public static TemplateToken generateIndexForm() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
|
||||
@ -610,7 +610,7 @@ public class TestAliasingFuzzer {
|
||||
for (int i = 0; i < indexFormNames.length; i++) {
|
||||
indexFormNames[i] = $("index" + i);
|
||||
}
|
||||
return body(
|
||||
return scope(
|
||||
"""
|
||||
// --- $test start ---
|
||||
""",
|
||||
@ -662,7 +662,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateArrayField(String name, MyType type) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("size", containerByteSize / type.byteSize()),
|
||||
let("name", name),
|
||||
let("type", type),
|
||||
@ -676,7 +676,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateMemorySegmentField(String name, MyType type) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("size", containerByteSize / type.byteSize()),
|
||||
let("byteSize", containerByteSize),
|
||||
let("name", name),
|
||||
@ -698,7 +698,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateIndexField(String name, IndexForm form) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("name", name),
|
||||
let("form", form.generate()),
|
||||
"""
|
||||
@ -709,7 +709,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateTestFields(String[] invarRest, String[] containerNames, String[] indexFormNames) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("ivType", isLongIvType ? "long" : "int"),
|
||||
"""
|
||||
// invarRest fields:
|
||||
@ -741,7 +741,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateContainerInitArray(String name) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("size", containerByteSize / containerElementType.byteSize()),
|
||||
let("name", name),
|
||||
"""
|
||||
@ -753,7 +753,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateContainerInitMemorySegment(String name) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("size", containerByteSize / containerElementType.byteSize()),
|
||||
let("name", name),
|
||||
"""
|
||||
@ -765,7 +765,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateContainerInit(String[] containerNames) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
// Init containers from original data:
|
||||
""",
|
||||
@ -784,7 +784,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateContainerAliasingAssignment(int i, String name1, String name2, String iterations) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("i", i),
|
||||
let("name1", name1),
|
||||
let("name2", name2),
|
||||
@ -798,7 +798,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateContainerAliasing(String[] containerNames, String iterations) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
// Container aliasing:
|
||||
""",
|
||||
@ -832,7 +832,7 @@ public class TestAliasingFuzzer {
|
||||
|
||||
if (accessIndexForm.length != 2) { throw new RuntimeException("not yet implemented"); }
|
||||
|
||||
var templateSplitRanges = Template.make(() -> body(
|
||||
var templateSplitRanges = Template.make(() -> scope(
|
||||
let("size", size),
|
||||
"""
|
||||
int middle = RANDOM.nextInt(#size / 3, #size * 2 / 3);
|
||||
@ -865,7 +865,7 @@ public class TestAliasingFuzzer {
|
||||
"""
|
||||
));
|
||||
|
||||
var templateWholeRanges = Template.make(() -> body(
|
||||
var templateWholeRanges = Template.make(() -> scope(
|
||||
let("size", size),
|
||||
"""
|
||||
var r0 = new IndexForm.Range(0, #size);
|
||||
@ -873,7 +873,7 @@ public class TestAliasingFuzzer {
|
||||
"""
|
||||
));
|
||||
|
||||
var templateRandomRanges = Template.make(() -> body(
|
||||
var templateRandomRanges = Template.make(() -> scope(
|
||||
let("size", size),
|
||||
"""
|
||||
int lo0 = RANDOM.nextInt(0, #size * 3 / 4);
|
||||
@ -883,7 +883,7 @@ public class TestAliasingFuzzer {
|
||||
"""
|
||||
));
|
||||
|
||||
var templateSmallOverlapRanges = Template.make(() -> body(
|
||||
var templateSmallOverlapRanges = Template.make(() -> scope(
|
||||
// Idea: same size ranges, with size "range". A small overlap,
|
||||
// so that bad runtime checks would create wrong results.
|
||||
let("size", size),
|
||||
@ -907,7 +907,7 @@ public class TestAliasingFuzzer {
|
||||
// -> safe with rnd = size/10
|
||||
));
|
||||
|
||||
var templateAnyRanges = Template.make(() -> body(
|
||||
var templateAnyRanges = Template.make(() -> scope(
|
||||
switch(RANDOM.nextInt(4)) {
|
||||
case 0 -> templateSplitRanges.asToken();
|
||||
case 1 -> templateWholeRanges.asToken();
|
||||
@ -917,7 +917,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
));
|
||||
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
// Generate ranges:
|
||||
""",
|
||||
@ -941,7 +941,7 @@ public class TestAliasingFuzzer {
|
||||
// We want there to be at least 1000 iterations.
|
||||
final int minIvRange = ivStrideAbs * 1000;
|
||||
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("containerByteSize", containerByteSize),
|
||||
"""
|
||||
// Compute loop bounds and loop invariants.
|
||||
@ -949,7 +949,7 @@ public class TestAliasingFuzzer {
|
||||
int ivHi = ivLo + #containerByteSize;
|
||||
""",
|
||||
IntStream.range(0, indexFormNames.length).mapToObj(i ->
|
||||
Template.make(() -> body(
|
||||
Template.make(() -> scope(
|
||||
let("i", i),
|
||||
let("form", indexFormNames[i]),
|
||||
"""
|
||||
@ -990,7 +990,7 @@ public class TestAliasingFuzzer {
|
||||
""",
|
||||
IntStream.range(0, indexFormNames.length).mapToObj(i1 ->
|
||||
IntStream.range(0, i1).mapToObj(i2 ->
|
||||
Template.make(() -> body(
|
||||
Template.make(() -> scope(
|
||||
let("i1", i1),
|
||||
let("i2", i2),
|
||||
// i1 < i2 or i1 > i2
|
||||
@ -1021,7 +1021,7 @@ public class TestAliasingFuzzer {
|
||||
|
||||
|
||||
private TemplateToken generateCallMethod(String output, String methodName, String containerPrefix) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("output", output),
|
||||
let("methodName", methodName),
|
||||
"var #output = #methodName(",
|
||||
@ -1034,7 +1034,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateIRRules() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
switch (containerKind) {
|
||||
case ContainerKind.ARRAY ->
|
||||
generateIRRulesArray();
|
||||
@ -1094,7 +1094,7 @@ public class TestAliasingFuzzer {
|
||||
// Regular array-accesses are vectorized quite predictably, and we can create nice
|
||||
// IR rules - even for cases where we do not expect vectorization.
|
||||
private TemplateToken generateIRRulesArray() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("T", containerElementType.letter()),
|
||||
switch (accessScenario) {
|
||||
case COPY_LOAD_STORE ->
|
||||
@ -1145,7 +1145,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateIRRulesMemorySegmentAtIndex() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
// Unfortunately, there are some issues that prevent RangeCheck elimination.
|
||||
// The cases are currently quite unpredictable, so we cannot create any IR
|
||||
@ -1158,7 +1158,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateIRRulesMemorySegmentLongAdrStride() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
// Unfortunately, there are some issues that prevent RangeCheck elimination.
|
||||
// The cases are currently quite unpredictable, so we cannot create any IR
|
||||
@ -1169,7 +1169,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateIRRulesMemorySegmentLongAdrScale() {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
// Unfortunately, there are some issues that prevent RangeCheck elimination.
|
||||
// The cases are currently quite unpredictable, so we cannot create any IR
|
||||
@ -1180,7 +1180,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateTestMethod(String methodName, String[] invarRest) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("methodName", methodName),
|
||||
let("containerElementType", containerElementType),
|
||||
let("ivStrideAbs", ivStrideAbs),
|
||||
@ -1230,7 +1230,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateTestLoopIterationArray(String[] invarRest) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("type", containerElementType),
|
||||
switch (accessScenario) {
|
||||
case COPY_LOAD_STORE ->
|
||||
@ -1245,7 +1245,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateTestLoopIterationMemorySegmentAtIndex(String[] invarRest) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("type0", accessType[0]),
|
||||
let("type1", accessType[1]),
|
||||
let("type0Layout", accessType[0].layout()),
|
||||
@ -1265,7 +1265,7 @@ public class TestAliasingFuzzer {
|
||||
}
|
||||
|
||||
private TemplateToken generateTestLoopIterationMemorySegmentLongAdr(String[] invarRest) {
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
let("type0", accessType[0]),
|
||||
let("type1", accessType[1]),
|
||||
let("type0Layout", accessType[0].layout()),
|
||||
|
||||
@ -43,7 +43,7 @@ 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.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
|
||||
/**
|
||||
@ -96,7 +96,7 @@ public class TestAdvanced {
|
||||
// - 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(
|
||||
var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope(
|
||||
let("con1", generator.next()),
|
||||
let("con2", generator.next()),
|
||||
"""
|
||||
@ -116,7 +116,7 @@ public class TestAdvanced {
|
||||
));
|
||||
|
||||
// Template for the Class.
|
||||
var classTemplate = Template.make("types", (List<Type> types) -> body(
|
||||
var classTemplate = Template.make("types", (List<Type> types) -> scope(
|
||||
let("classpath", comp.getEscapedClassPathOfCompiledClasses()),
|
||||
"""
|
||||
package p.xyz;
|
||||
|
||||
@ -40,7 +40,7 @@ import java.util.Set;
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import compiler.lib.template_framework.library.Expression;
|
||||
import compiler.lib.template_framework.library.Operations;
|
||||
@ -78,7 +78,7 @@ public class TestExpressions {
|
||||
// precision results from some operators. We only compare the results if we know that the
|
||||
// result is deterministically the same.
|
||||
TemplateToken expressionToken = expression.asToken(expression.argumentTypes.stream().map(t -> t.con()).toList());
|
||||
return body(
|
||||
return scope(
|
||||
let("returnType", expression.returnType),
|
||||
"""
|
||||
@Test
|
||||
|
||||
@ -41,7 +41,8 @@ import java.util.HashMap;
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.transparentScope;
|
||||
import static compiler.lib.template_framework.Template.dataNames;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import static compiler.lib.template_framework.Template.$;
|
||||
@ -77,7 +78,7 @@ public class TestPrimitiveTypes {
|
||||
Map<String, TemplateToken> tests = new HashMap<>();
|
||||
|
||||
// The boxing tests check if we can autobox with "boxedTypeName".
|
||||
var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body(
|
||||
var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope(
|
||||
let("CON1", type.con()),
|
||||
let("CON2", type.con()),
|
||||
let("Boxed", type.boxedTypeName()),
|
||||
@ -99,7 +100,7 @@ public class TestPrimitiveTypes {
|
||||
}
|
||||
|
||||
// Integral and Float types have a size. Also test if "isFloating" is correct.
|
||||
var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body(
|
||||
var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope(
|
||||
let("size", type.byteSize()),
|
||||
let("isFloating", type.isFloating()),
|
||||
"""
|
||||
@ -129,27 +130,31 @@ public class TestPrimitiveTypes {
|
||||
|
||||
// Finally, test the type by creating some DataNames (variables), and sampling
|
||||
// from them. There should be no cross-over between the types.
|
||||
var variableTemplate = Template.make("type", (PrimitiveType type) -> body(
|
||||
// IMPORTANT: since we are adding the DataName via an inserted Template, we
|
||||
// must chose a "transparentScope", so that the DataName escapes. If we
|
||||
// instead chose "scope", the test would fail, because it later
|
||||
// finds no DataNames when we sample.
|
||||
var variableTemplate = Template.make("type", (PrimitiveType type) -> transparentScope(
|
||||
let("CON", type.con()),
|
||||
addDataName($("var"), type, MUTABLE),
|
||||
addDataName($("var"), type, MUTABLE), // escapes the Template
|
||||
"""
|
||||
#type $var = #CON;
|
||||
"""
|
||||
));
|
||||
|
||||
var sampleTemplate = Template.make("type", (PrimitiveType type) -> body(
|
||||
let("var", dataNames(MUTABLE).exactOf(type).sample().name()),
|
||||
var sampleTemplate = Template.make("type", (PrimitiveType type) -> scope(
|
||||
let("CON", type.con()),
|
||||
dataNames(MUTABLE).exactOf(type).sampleAndLetAs("var"),
|
||||
"""
|
||||
#var = #CON;
|
||||
"""
|
||||
));
|
||||
|
||||
var namesTemplate = Template.make(() -> body(
|
||||
var namesTemplate = Template.make(() -> scope(
|
||||
"""
|
||||
public static void test_names() {
|
||||
""",
|
||||
Hooks.METHOD_HOOK.anchor(
|
||||
Hooks.METHOD_HOOK.anchor(scope(
|
||||
Collections.nCopies(10,
|
||||
CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type ->
|
||||
Hooks.METHOD_HOOK.insert(variableTemplate.asToken(type))
|
||||
@ -161,7 +166,7 @@ public class TestPrimitiveTypes {
|
||||
Collections.nCopies(10,
|
||||
CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList()
|
||||
)
|
||||
),
|
||||
)),
|
||||
"""
|
||||
}
|
||||
"""
|
||||
@ -172,7 +177,7 @@ public class TestPrimitiveTypes {
|
||||
// Test runtime random value generation with LibraryRNG
|
||||
// Runtime random number generation of a given primitive type can be very helpful
|
||||
// when writing tests that require random inputs.
|
||||
var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> body(
|
||||
var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> scope(
|
||||
"""
|
||||
{
|
||||
// Fill an array with 1_000 random values. Every type has at least 2 values,
|
||||
@ -196,7 +201,7 @@ public class TestPrimitiveTypes {
|
||||
"""
|
||||
));
|
||||
|
||||
var libraryRNGTemplate = Template.make(() -> body(
|
||||
var libraryRNGTemplate = Template.make(() -> scope(
|
||||
// Make sure we instantiate the LibraryRNG class.
|
||||
PrimitiveType.generateLibraryRNG(),
|
||||
// Now we can use it inside the test.
|
||||
@ -213,7 +218,7 @@ public class TestPrimitiveTypes {
|
||||
|
||||
// Finally, put all the tests together in a class, and invoke all
|
||||
// tests from the main method.
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"""
|
||||
package p.xyz;
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ package template_framework.examples;
|
||||
|
||||
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.scope;
|
||||
|
||||
public class TestSimple {
|
||||
|
||||
@ -61,7 +61,7 @@ public class TestSimple {
|
||||
// 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(
|
||||
var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> scope(
|
||||
"""
|
||||
package p.xyz;
|
||||
public class InnerTest {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,7 @@ import compiler.lib.generators.Generators;
|
||||
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
|
||||
import compiler.lib.template_framework.library.Hooks;
|
||||
@ -82,7 +82,7 @@ public class TestWithTestFrameworkClass {
|
||||
// Generate a source Java file as String
|
||||
public static String generate(CompileFramework comp) {
|
||||
// A simple template that adds a comment.
|
||||
var commentTemplate = Template.make(() -> body(
|
||||
var commentTemplate = Template.make(() -> scope(
|
||||
"""
|
||||
// Comment inserted from test method to class hook.
|
||||
"""
|
||||
@ -103,7 +103,7 @@ public class TestWithTestFrameworkClass {
|
||||
// - The test method makes use of hashtag replacements (#con2 and #op).
|
||||
// - The Check method verifies the results of the test method with the
|
||||
// GOLD value.
|
||||
var testTemplate = Template.make("op", (String op) -> body(
|
||||
var testTemplate = Template.make("op", (String op) -> scope(
|
||||
let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()),
|
||||
let("con1", Generators.G.ints().next()),
|
||||
let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()),
|
||||
|
||||
@ -38,7 +38,7 @@ import java.util.Set;
|
||||
import compiler.lib.template_framework.DataName;
|
||||
import compiler.lib.template_framework.Template;
|
||||
import compiler.lib.template_framework.TemplateToken;
|
||||
import static compiler.lib.template_framework.Template.body;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import compiler.lib.template_framework.library.CodeGenerationDataNameType;
|
||||
import compiler.lib.template_framework.library.Expression;
|
||||
|
||||
@ -93,7 +93,7 @@ public class TestExpression {
|
||||
Expression e3 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, "]");
|
||||
Expression e4 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, ",", myTypeA, "]");
|
||||
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"xx", e1.toString(), "yy\n",
|
||||
"xx", e2.toString(), "yy\n",
|
||||
"xx", e3.toString(), "yy\n",
|
||||
@ -141,7 +141,7 @@ public class TestExpression {
|
||||
Expression e3e1 = e3.nest(0, e1);
|
||||
Expression e4e5 = e4.nest(1, e5);
|
||||
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"xx", e1e1.toString(), "yy\n",
|
||||
"xx", e2e1.toString(), "yy\n",
|
||||
"xx", e3e1.toString(), "yy\n",
|
||||
@ -184,7 +184,7 @@ public class TestExpression {
|
||||
// Alternating pattern
|
||||
Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5);
|
||||
|
||||
var template = Template.make(() -> body(
|
||||
var template = Template.make(() -> scope(
|
||||
"xx", e1e2.toString(), "yy\n",
|
||||
"xx", e1ex.toString(), "yy\n",
|
||||
"xx", e1e4.toString(), "yy\n",
|
||||
|
||||
@ -39,7 +39,7 @@ 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.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
|
||||
public class TestFormat {
|
||||
@ -84,7 +84,7 @@ public class TestFormat {
|
||||
|
||||
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(
|
||||
var template1 = Template.make("info", (FormatInfo info) -> scope(
|
||||
let("id", info.id()),
|
||||
let("type", info.type()),
|
||||
let("value", info.value()),
|
||||
@ -95,7 +95,7 @@ public class TestFormat {
|
||||
));
|
||||
|
||||
// For each FormatInfo in list, generate the "get" methods inside InnerTest class.
|
||||
var template2 = Template.make(() -> body(
|
||||
var template2 = Template.make(() -> scope(
|
||||
"""
|
||||
package p.xyz;
|
||||
public class InnerTest {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user