@@ -46,14 +46,21 @@ private SiftPatterns() {
4646 * @param option2 The second mandatory alternative.
4747 * @param additionalOptions Any further alternative patterns.
4848 * @return A composable {@link SiftPattern} representing the logical OR.
49+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
50+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
51+ * Reusable blocks must be unanchored.
4952 */
5053 public static SiftPattern anyOf (SiftPattern option1 , SiftPattern option2 , SiftPattern ... additionalOptions ) {
5154 Objects .requireNonNull (option1 , "First option cannot be null" );
5255 Objects .requireNonNull (option2 , "Second option cannot be null" );
5356 Objects .requireNonNull (additionalOptions , "Additional options array cannot be null" );
5457
58+ requireUnanchored (option1 );
59+ requireUnanchored (option2 );
60+
5561 for (SiftPattern opt : additionalOptions ) {
5662 Objects .requireNonNull (opt , "Additional option cannot be null" );
63+ requireUnanchored (opt );
5764 }
5865
5966 return memoize (() -> {
@@ -80,6 +87,9 @@ public static SiftPattern anyOf(SiftPattern option1, SiftPattern option2, SiftPa
8087 * @param patterns A list of SiftPatterns.
8188 * @return A SiftPattern combining the provided options.
8289 * @throws IllegalArgumentException if the list is null or empty.
90+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
91+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
92+ * Reusable blocks must be unanchored.
8393 */
8494 public static SiftPattern anyOf (java .util .List <? extends SiftPattern > patterns ) {
8595 if (patterns == null || patterns .isEmpty ()) {
@@ -89,6 +99,10 @@ public static SiftPattern anyOf(java.util.List<? extends SiftPattern> patterns)
8999 return patterns .get (0 ); // QoL Optimization: no need to wrap a single element
90100 }
91101
102+ for (SiftPattern pattern : patterns ) {
103+ requireUnanchored (pattern );
104+ }
105+
92106 return memoize (() -> {
93107 StringBuilder sb = new StringBuilder ();
94108 sb .append (RegexSyntax .NON_CAPTURING_GROUP_OPEN );
@@ -117,9 +131,13 @@ public static SiftPattern anyOf(java.util.List<? extends SiftPattern> patterns)
117131 *
118132 * @param pattern The pattern to capture.
119133 * @return A SiftPattern wrapped in parentheses.
134+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
135+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
136+ * Reusable blocks must be unanchored.
120137 */
121138 public static SiftPattern capture (SiftPattern pattern ) {
122139 Objects .requireNonNull (pattern , "Pattern to capture cannot be null" );
140+ requireUnanchored (pattern );
123141 return memoize (() -> RegexSyntax .GROUP_OPEN + pattern .shake () + RegexSyntax .GROUP_CLOSE );
124142 }
125143
@@ -140,9 +158,13 @@ public static SiftPattern capture(SiftPattern pattern) {
140158 *
141159 * @param pattern The pattern that must follow.
142160 * @return A SiftPattern representing the positive lookahead.
161+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
162+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
163+ * Reusable blocks must be unanchored.
143164 */
144165 public static SiftPattern positiveLookahead (SiftPattern pattern ) {
145166 Objects .requireNonNull (pattern , "Lookahead pattern cannot be null" );
167+ requireUnanchored (pattern );
146168 return memoize (() -> RegexSyntax .POSITIVE_LOOKAHEAD_OPEN + pattern .shake () + RegexSyntax .GROUP_CLOSE );
147169 }
148170
@@ -163,9 +185,13 @@ public static SiftPattern positiveLookahead(SiftPattern pattern) {
163185 *
164186 * @param pattern The pattern that must not follow.
165187 * @return A SiftPattern representing the negative lookahead.
188+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
189+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
190+ * Reusable blocks must be unanchored.
166191 */
167192 public static SiftPattern negativeLookahead (SiftPattern pattern ) {
168193 Objects .requireNonNull (pattern , "Lookahead pattern cannot be null" );
194+ requireUnanchored (pattern );
169195 return memoize (() -> RegexSyntax .NEGATIVE_LOOKAHEAD_OPEN + pattern .shake () + RegexSyntax .GROUP_CLOSE );
170196 }
171197
@@ -186,9 +212,13 @@ public static SiftPattern negativeLookahead(SiftPattern pattern) {
186212 *
187213 * @param pattern The pattern that must precede.
188214 * @return A SiftPattern representing the positive lookbehind.
215+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
216+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
217+ * Reusable blocks must be unanchored.
189218 */
190219 public static SiftPattern positiveLookbehind (SiftPattern pattern ) {
191220 Objects .requireNonNull (pattern , "Lookbehind pattern cannot be null" );
221+ requireUnanchored (pattern );
192222 return memoize (() -> RegexSyntax .POSITIVE_LOOKBEHIND_OPEN + pattern .shake () + RegexSyntax .GROUP_CLOSE );
193223 }
194224
@@ -209,9 +239,13 @@ public static SiftPattern positiveLookbehind(SiftPattern pattern) {
209239 *
210240 * @param pattern The pattern that must not precede.
211241 * @return A SiftPattern representing the negative lookbehind.
242+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
243+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
244+ * Reusable blocks must be unanchored.
212245 */
213246 public static SiftPattern negativeLookbehind (SiftPattern pattern ) {
214247 Objects .requireNonNull (pattern , "Lookbehind pattern cannot be null" );
248+ requireUnanchored (pattern );
215249 return memoize (() -> RegexSyntax .NEGATIVE_LOOKBEHIND_OPEN + pattern .shake () + RegexSyntax .GROUP_CLOSE );
216250 }
217251
@@ -225,9 +259,13 @@ public static SiftPattern negativeLookbehind(SiftPattern pattern) {
225259 * @param pattern The pattern to capture within this group.
226260 * @return A NamedCapture definition.
227261 * @throws IllegalArgumentException if {@code groupName} is null, empty, starts with a digit, or contains non-alphanumeric characters (e.g., spaces, underscores, or symbols).
262+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
263+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
264+ * Reusable blocks must be unanchored.
228265 */
229266 public static NamedCapture capture (String groupName , SiftPattern pattern ) {
230267 Objects .requireNonNull (pattern , "Pattern to capture cannot be null" );
268+ requireUnanchored (pattern );
231269 GroupName validatedName = GroupName .of (groupName );
232270 return new NamedCapture (validatedName , pattern );
233271 }
@@ -241,13 +279,19 @@ public static NamedCapture capture(String groupName, SiftPattern pattern) {
241279 * @param first The first required pattern.
242280 * @param then Optional additional patterns to include in the same group.
243281 * @return A SiftPattern representing the concatenated non-capturing group.
282+ * @throws IllegalStateException if the provided pattern contains absolute boundaries
283+ * (e.g., created with {@code fromStart()} or closed with {@code andNothingElse()}).
284+ * Reusable blocks must be unanchored.
244285 */
245286 public static SiftPattern group (SiftPattern first , SiftPattern ... then ) {
246287 Objects .requireNonNull (first , "First pattern in group cannot be null" );
247288 Objects .requireNonNull (then , "Additional patterns array cannot be null" );
248289
290+ requireUnanchored (first );
291+
249292 for (SiftPattern opt : then ) {
250293 Objects .requireNonNull (opt , "Additional option cannot be null" );
294+ requireUnanchored (opt );
251295 }
252296
253297 return memoize (() -> {
@@ -370,6 +414,25 @@ public Pattern sieve() {
370414 public void preventExternalImplementation (InternalToken unused ) {
371415 // unused intentionally to prevent external implementations
372416 }
417+
418+ @ Override
419+ public boolean hasAbsoluteBoundaries () {
420+ // SiftPatterns components are explicitly validated to be unanchored
421+ // before this memoized instance is created.
422+ return false ;
423+ }
373424 };
374425 }
426+
427+ /**
428+ * Internal security check to prevent nesting of anchored patterns.
429+ */
430+ private static void requireUnanchored (SiftPattern pattern ) {
431+ if (pattern .hasAbsoluteBoundaries ()) {
432+ throw new IllegalStateException (
433+ "Composition Error: Cannot embed a pattern that contains absolute boundaries " +
434+ "(like fromStart() or andNothingElse()). Reusable blocks must be unanchored."
435+ );
436+ }
437+ }
375438}
0 commit comments