Skip to content

Implement dynamic scoping for regex match variables#249

Merged
fglock merged 2 commits intomasterfrom
fix-bytecode-my-extraction
Feb 27, 2026
Merged

Implement dynamic scoping for regex match variables#249
fglock merged 2 commits intomasterfrom
fix-bytecode-my-extraction

Conversation

@fglock
Copy link
Owner

@fglock fglock commented Feb 27, 2026

Summary

  • Match variables ($1, $&, $`, $', etc.) are now dynamically scoped per subroutine call, matching Perl's behavior
  • Uses the existing DynamicVariableManager + DynamicState infrastructure (same as local variables)
  • Covers both JVM-compiled and bytecode-interpreted code paths
  • Return values containing match variable proxies are materialized before scope exit

Implementation

  • RuntimeRegexState — new DynamicState that saves/restores the 10 static fields on RuntimeRegex
  • JVM backendLocal.localSetup() detects matchRegex/replaceRegex ops and emits RuntimeRegexState.pushLocal(); localTeardown pops via popToLocalLevel()
  • Interpreter backendInterpretedCode.containsRegex flag set by BytecodeCompiler; BytecodeInterpreter.execute() saves/restores in try/finally
  • Proxy resolutionScalarSpecialVariable.getList() materializes eagerly; RuntimeList.resolveMatchProxies() called at return boundaries before teardown

Test plan

  • 154/154 unit tests pass
  • Single return $1 returns callee's match, caller's $1 restored
  • List return ($1, $2) returns callee's matches, caller's $1/$2 restored
  • Failed match in callee doesn't clobber caller's match vars
  • eval STRING with regex doesn't clobber caller's match vars

Generated with Devin

fglock and others added 2 commits February 27, 2026 15:55
Match variables are now saved/restored per subroutine call using the
existing DynamicVariableManager infrastructure, matching Perl behavior
where regex match state is dynamically scoped to the enclosing block.

JVM backend: Local.localSetup detects matchRegex/replaceRegex in AST
and emits RuntimeRegexState.pushLocal(); localTeardown pops via
DynamicVariableManager.popToLocalLevel().

Interpreter backend: BytecodeCompiler sets containsRegex flag on
InterpretedCode; BytecodeInterpreter.execute() saves/restores in
try/finally based on the flag.

Proxy resolution: ScalarSpecialVariable.getList() now materializes
the value eagerly, and RuntimeList.resolveMatchProxies() is called
before local teardown at return boundaries so that return values
like ($1, $2) capture the callee match state, not the restored
caller state.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
When eval STRING runs, it creates temporary aliases in the global
namespace so that BEGIN blocks inside the eval can access outer lexical
variables. These aliases were not being cleaned up after compilation,
causing `my` variable re-declarations in loops to pick up stale values
via retrieveBeginScalar instead of creating fresh objects.

This fix tracks the alias keys created during eval STRING compilation
and removes them in the finally block after parsing/compilation is done.

Fixes ExifTool test 22 (InsertTagValues) where `my` variables inside a
while loop containing `eval $expr` were not being re-initialized on
each iteration.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock merged commit cb10004 into master Feb 27, 2026
2 checks passed
@fglock fglock deleted the fix-bytecode-my-extraction branch February 27, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant