-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathRuntimeRegexTemplate.java
More file actions
117 lines (107 loc) · 5.02 KB
/
Copy pathRuntimeRegexTemplate.java
File metadata and controls
117 lines (107 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package org.perlonjava.runtime.regex;
import org.perlonjava.runtime.runtimetypes.RuntimeBase;
import org.perlonjava.runtime.runtimetypes.RuntimeList;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
import org.perlonjava.runtime.runtimetypes.RuntimeScalarType;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Runtime interpolation result that keeps executable regex callbacks out of strings. */
public final class RuntimeRegexTemplate {
private static final Pattern CALLOUT_ID = Pattern.compile(
"\\(\\?\\{=(CALL|DYNAMIC):(\\d+)\\}\\)");
private final String pattern;
private final List<RuntimeRegexCallback> callbacks;
private RuntimeRegexTemplate(String pattern, List<RuntimeRegexCallback> callbacks) {
this.pattern = pattern;
this.callbacks = List.copyOf(callbacks);
}
public static RuntimeScalar build(RuntimeList parts) {
if (parts.elements.size() == 1) {
RuntimeScalar only = parts.elements.getFirst().scalar();
// A lone interpolation must retain its runtime type so qr
// overloading and an already-compiled regex remain observable.
// Only a parser-created callback needs a new template skeleton.
if (!(only.value instanceof RuntimeRegexCallback)) return only;
}
StringBuilder pattern = new StringBuilder();
List<RuntimeRegexCallback> callbacks = new ArrayList<>();
boolean tainted = false;
for (RuntimeBase part : parts.elements) {
RuntimeScalar scalar = part.scalar();
// Interpolation is one scalar read. Resolve tied magic once, then
// use that materialized value for type inspection, taint, and text.
if (scalar.type == RuntimeScalarType.TIED_SCALAR) {
scalar = scalar.tiedFetch();
}
tainted |= scalar.isTainted();
if (scalar.value instanceof RuntimeRegexCallback callback) {
int id = callbacks.size();
callbacks.add(callback);
if (callback.kind == RuntimeRegexCallback.Kind.CONDITION) {
pattern.append("?{=CALL:").append(id).append("})");
} else if (callback.kind == RuntimeRegexCallback.Kind.DYNAMIC) {
pattern.append("(?{=DYNAMIC:").append(id).append("})");
} else {
pattern.append("(?{=CALL:").append(id).append("})");
}
} else if (scalar.value instanceof RuntimeRegex regex
&& !regex.executableCallbacks.isEmpty()) {
appendEmbeddedRegex(pattern, callbacks, regex.toExecutableString(), regex.executableCallbacks);
} else if (scalar.value instanceof RuntimeRegexTemplate template) {
appendEmbeddedRegex(pattern, callbacks, template.pattern, template.callbacks);
} else {
pattern.append(scalar);
}
}
RuntimeScalar result = callbacks.isEmpty()
? new RuntimeScalar(pattern.toString())
: new RuntimeScalar(new RuntimeRegexTemplate(pattern.toString(), callbacks));
result.tainted = tainted;
return result;
}
private static void appendEmbeddedRegex(StringBuilder pattern,
List<RuntimeRegexCallback> callbacks,
String embeddedPattern,
List<RuntimeRegexCallback> embeddedCallbacks) {
int offset = callbacks.size();
Matcher matcher = CALLOUT_ID.matcher(embeddedPattern);
StringBuilder remapped = new StringBuilder();
while (matcher.find()) {
String kind = matcher.group(1);
int oldId = Integer.parseInt(matcher.group(2));
if (oldId < 0 || oldId >= embeddedCallbacks.size()) {
throw new IllegalArgumentException("Invalid embedded regex callout ID " + oldId);
}
matcher.appendReplacement(remapped,
"(?{=" + kind + ":" + (offset + oldId) + "})");
}
matcher.appendTail(remapped);
pattern.append(remapped);
callbacks.addAll(embeddedCallbacks);
}
String pattern() {
return pattern;
}
List<RuntimeRegexCallback> callbacks() {
return callbacks;
}
static String displayPattern(String executablePattern) {
if (executablePattern == null || executablePattern.isEmpty()) {
return executablePattern;
}
Matcher matcher = CALLOUT_ID.matcher(executablePattern);
StringBuilder display = new StringBuilder();
while (matcher.find()) {
String replacement = "DYNAMIC".equals(matcher.group(1)) ? "(??{})" : "(?{})";
matcher.appendReplacement(display, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(display);
return display.toString();
}
@Override
public String toString() {
return pattern;
}
}