mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-27 18:50:07 +00:00
8364393: Allow templates to have # character without variable replacement
Reviewed-by: epeter, chagedorn
This commit is contained in:
parent
a39a1f10f7
commit
3d8ffabe5d
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -413,8 +413,39 @@ final class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index where the next replacement pattern after {@code start} begins while skipping
|
||||
* over "$$" and "##".
|
||||
*
|
||||
* @param s string to search for replacements
|
||||
* @param start index from which to start searching
|
||||
* @return the index of the beginning of the next replacement pattern or the length of {@code s}
|
||||
*/
|
||||
private int findNextReplacement(final String s, final int start) {
|
||||
int next = start;
|
||||
for (int potentialStart = start; potentialStart < s.length() && s.charAt(next) == s.charAt(potentialStart); potentialStart = next + 1) {
|
||||
// If this is not the first iteration, we have found a doubled up "$" or "#" and need to skip
|
||||
// over the second instance.
|
||||
if (potentialStart != start) {
|
||||
potentialStart += 1;
|
||||
}
|
||||
// Find the next "$" or "#", after the potential start.
|
||||
int dollar = s.indexOf("$", potentialStart);
|
||||
int hashtag = s.indexOf("#", potentialStart);
|
||||
// If the character was not found, we want to have the rest of the
|
||||
// String s, so instead of "-1" take the end/length of the String.
|
||||
dollar = (dollar == -1) ? s.length() : dollar;
|
||||
hashtag = (hashtag == -1) ? s.length() : hashtag;
|
||||
// Take the first one.
|
||||
next = Math.min(dollar, hashtag);
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* We split a {@link String} by "#" and "$", and then look at each part.
|
||||
* However, we escape "##" to "#" and "$$" to "$".
|
||||
* Example:
|
||||
*
|
||||
* s: "abcdefghijklmnop #name abcdefgh${var_name} 12345#{name2}_con $field_name something"
|
||||
@ -428,16 +459,19 @@ final class Renderer {
|
||||
int start = 0;
|
||||
boolean startIsAfterDollar = false;
|
||||
do {
|
||||
// Find the next "$" or "#", after start.
|
||||
int dollar = s.indexOf("$", start);
|
||||
int hashtag = s.indexOf("#", start);
|
||||
// If the character was not found, we want to have the rest of the
|
||||
// String s, so instead of "-1" take the end/length of the String.
|
||||
dollar = (dollar == -1) ? s.length() : dollar;
|
||||
hashtag = (hashtag == -1) ? s.length() : hashtag;
|
||||
// Take the first one.
|
||||
int next = Math.min(dollar, hashtag);
|
||||
int next = findNextReplacement(s, start);
|
||||
|
||||
// Detect most zero sized replacement patterns, i.e. "$#" or "#$", for better error reporting.
|
||||
if (next < s.length() - 2 && ((s.charAt(next) == '$' && s.charAt(next + 1) == '#') ||
|
||||
(s.charAt(next) == '#' && s.charAt(next + 1) == '$'))) {
|
||||
String pattern = s.substring(next, next + 2);
|
||||
throw new RendererException("Found zero sized replacement pattern '" + pattern + "'.");
|
||||
}
|
||||
|
||||
String part = s.substring(start, next);
|
||||
// Escape doubled up replacement characters.
|
||||
part = part.replace("##", "#");
|
||||
part = part.replace("$$", "$");
|
||||
|
||||
if (count == 0) {
|
||||
// First part has no "#" or "$" before it.
|
||||
@ -452,8 +486,8 @@ final class Renderer {
|
||||
// terminate now.
|
||||
return;
|
||||
}
|
||||
start = next + 1; // skip over the "#" or "$"
|
||||
startIsAfterDollar = next == dollar; // remember which character we just split with
|
||||
start = next + 1;
|
||||
startIsAfterDollar = s.charAt(next) == '$'; // remember which character we just split with
|
||||
count++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
@ -160,7 +160,8 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* arguments into the strings. But since string templates are not (yet) available, the Templates provide
|
||||
* <strong>hashtag replacements</strong> in the {@link String}s: the Template argument names are captured, and
|
||||
* the argument values automatically replace any {@code "#name"} in the {@link String}s. See the different overloads
|
||||
* of {@link #make} for examples. Additional hashtag replacements can be defined with {@link #let}.
|
||||
* of {@link #make} for examples. Additional hashtag replacements can be defined with {@link #let}. If a "#" is needed
|
||||
* in the code, hashtag replacmement can be escaped by writing two hashtags, i.e. "##" will render as "#".
|
||||
* 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
|
||||
@ -172,7 +173,8 @@ import compiler.lib.ir_framework.TestFramework;
|
||||
* For this, Templates provide <strong>dollar replacements</strong>, which automatically rename any
|
||||
* {@code "$name"} in the {@link String} with a {@code "name_ID"}, where the {@code "ID"} is unique for every use of
|
||||
* a Template. The dollar replacement can also be captured with {@link #$}, and passed to nested
|
||||
* Templates, which allows sharing of these identifier names between Templates.
|
||||
* Templates, which allows sharing of these identifier names between Templates. Similar to hashtag replacements,
|
||||
* dollars can be escaped by doubling up, i.e. "$$" renders as "$".
|
||||
*
|
||||
* <p>
|
||||
* The dollar and hashtag names must have at least one character. The first character must be a letter
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -226,6 +226,8 @@ public class TestTutorial {
|
||||
// we automatically rename the names that have a $ prepended with
|
||||
// var_1, var_2, etc.
|
||||
"""
|
||||
// You can escape a hashtag by doubling it up. e.g. ## will render as a
|
||||
// single hashtag. The same goes for $$.
|
||||
int $var = #con;
|
||||
System.out.println("T1: #x, #con, " + $var);
|
||||
"""
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 2026, 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
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8344942
|
||||
* @bug 8344942 8364393
|
||||
* @summary Test some basic Template instantiations. We do not necessarily generate correct
|
||||
* java code, we just test that the code generation deterministically creates the
|
||||
* expected String.
|
||||
@ -138,6 +138,7 @@ public class TestTemplate {
|
||||
testHookWithNestedTemplates();
|
||||
testHookRecursion();
|
||||
testDollar();
|
||||
testEscaping();
|
||||
testLet1();
|
||||
testLet2();
|
||||
testDollarAndHashtagBrackets();
|
||||
@ -182,7 +183,6 @@ public class TestTemplate {
|
||||
expectRendererException(() -> testFailingDollarName5(), "Is not a valid '$' replacement pattern: '$' in '$'.");
|
||||
expectRendererException(() -> testFailingDollarName6(), "Is not a valid '$' replacement pattern: '$' in 'asdf$'.");
|
||||
expectRendererException(() -> testFailingDollarName7(), "Is not a valid '$' replacement pattern: '$1' in 'asdf$1'.");
|
||||
expectRendererException(() -> testFailingDollarName8(), "Is not a valid '$' replacement pattern: '$' in 'abc$$abc'.");
|
||||
expectRendererException(() -> testFailingLetName1(), "A hashtag replacement should not be null.");
|
||||
expectRendererException(() -> testFailingHashtagName1(), "Is not a valid hashtag replacement name: ''.");
|
||||
expectRendererException(() -> testFailingHashtagName2(), "Is not a valid hashtag replacement name: 'abc#abc'.");
|
||||
@ -191,11 +191,12 @@ public class TestTemplate {
|
||||
expectRendererException(() -> testFailingHashtagName5(), "Is not a valid '#' replacement pattern: '#' in '#'.");
|
||||
expectRendererException(() -> testFailingHashtagName6(), "Is not a valid '#' replacement pattern: '#' in 'asdf#'.");
|
||||
expectRendererException(() -> testFailingHashtagName7(), "Is not a valid '#' replacement pattern: '#1' in 'asdf#1'.");
|
||||
expectRendererException(() -> testFailingHashtagName8(), "Is not a valid '#' replacement pattern: '#' in 'abc##abc'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName1(), "Is not a valid '#' replacement pattern: '#' in '#$'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName2(), "Is not a valid '$' replacement pattern: '$' in '$#'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName3(), "Is not a valid '#' replacement pattern: '#' in '#$name'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName4(), "Is not a valid '$' replacement pattern: '$' in '$#name'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName3(), "Found zero sized replacement pattern '#$'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName4(), "Found zero sized replacement pattern '$#'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName5(), "Found zero sized replacement pattern '#$'.");
|
||||
expectRendererException(() -> testFailingDollarHashtagName6(), "Found zero sized replacement pattern '$#'.");
|
||||
expectRendererException(() -> testFailingHook(), "Hook 'Hook1' was referenced but not found!");
|
||||
expectRendererException(() -> testFailingSample1a(), "No Name found for DataName.FilterdSet(MUTABLE, subtypeOf(int), supertypeOf(int))");
|
||||
expectRendererException(() -> testFailingSample1b(), "No Name found for StructuralName.FilteredSet( subtypeOf(StructuralA) supertypeOf(StructuralA))");
|
||||
@ -822,6 +823,60 @@ public class TestTemplate {
|
||||
checkEQ(code, expected);
|
||||
}
|
||||
|
||||
public static void testEscaping() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
let("one", 1),
|
||||
let("two", 2),
|
||||
let("three", 3),
|
||||
"""
|
||||
abc##def
|
||||
abc$$def
|
||||
abc####def
|
||||
abc$$$$def
|
||||
##abc
|
||||
$$abc
|
||||
abc##
|
||||
abc$$
|
||||
##
|
||||
$$
|
||||
######
|
||||
$$$$$$
|
||||
abc###one
|
||||
abc$$$dollar
|
||||
#one ###two #####three
|
||||
###{one}##
|
||||
$dollar $$$dollar $$$$$dollar
|
||||
$$${dollar}$$
|
||||
##$dollar $$#one $$##$$##
|
||||
"""
|
||||
));
|
||||
|
||||
String code = template1.render();
|
||||
String expected =
|
||||
"""
|
||||
abc#def
|
||||
abc$def
|
||||
abc##def
|
||||
abc$$def
|
||||
#abc
|
||||
$abc
|
||||
abc#
|
||||
abc$
|
||||
#
|
||||
$
|
||||
###
|
||||
$$$
|
||||
abc#1
|
||||
abc$dollar_1
|
||||
1 #2 ##3
|
||||
#1#
|
||||
dollar_1 $dollar_1 $$dollar_1
|
||||
$dollar_1$
|
||||
#dollar_1 $1 $#$#
|
||||
""";
|
||||
checkEQ(code, expected);
|
||||
}
|
||||
|
||||
public static void testLet1() {
|
||||
var hook1 = new Hook("Hook1");
|
||||
|
||||
@ -3382,13 +3437,6 @@ public class TestTemplate {
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingDollarName8() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"abc$$abc" // empty dollar name
|
||||
));
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingLetName1() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
let(null, $("abc")) // Null input for hashtag name
|
||||
@ -3447,13 +3495,6 @@ public class TestTemplate {
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingHashtagName8() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"abc##abc" // empty hashtag name
|
||||
));
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingDollarHashtagName1() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"#$" // empty hashtag name
|
||||
@ -3470,14 +3511,28 @@ public class TestTemplate {
|
||||
|
||||
public static void testFailingDollarHashtagName3() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"#$name" // empty hashtag name
|
||||
"#$name" // Zero sized replacement
|
||||
));
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingDollarHashtagName4() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"$#name" // empty dollar name
|
||||
"$#name" // Zero sized replacement
|
||||
));
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingDollarHashtagName5() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"asdf#$abc" // Zero sized replacement
|
||||
));
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
public static void testFailingDollarHashtagName6() {
|
||||
var template1 = Template.make(() -> scope(
|
||||
"asdf$#abc" // Zero sized replacement
|
||||
));
|
||||
String code = template1.render();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user