╔═╗╔═╗╦ ╔═╗ ╔╦╗╔═╗╔╦╗╦╔═╗╦╔═╗╔═╗ ╚═╗║╣ ║ ╠╣───║║║║ ║ ║║║╠╣ ║║╣ ╠╦╝ ╚═╝╚═╝╩═╝╚ ╩ ╩╚═╝═╩╝╩╚ ╩╚═╝╩╚═ ╦ ╔═╗╔═╗╔═╗╔═╗ ║ ║ ║║ ║╠═╝╚═╗ ╩═╝╚═╝╚═╝╩ ╚═╝ ============================================================================ Loops. For, while, until, foreach. Perl has plenty of ways to repeat yourself. But what if you wanted to build a loop out of eval and regex? What if your code rewrote itself on every iteration? #!/usr/bin/env perl use feature qw|say|; $_ = <<'EOF'; 10; s~(\d+)(?{ say qq($1) })~$1-1~e; sleep 1; $1 ? eval : say q(Countdown complete!); EOF eval; Run it. Watch it count down from 10 to 1. No for loop. No while. Just a heredoc that eats itself. ============================================================================ PART 1: THE SETUP ----------------- $_ = <<'EOF'; ...code... EOF We're stuffing a chunk of Perl code into $_ as a string. The single quotes around EOF mean no interpolation happens yet. It's just text. That text happens to be valid Perl. And we're about to run it. ============================================================================ PART 2: THE STARTING NUMBER --------------------------- 10; First line of our heredoc. Just a number sitting there. In Perl, a bare number is a valid statement. It evaluates to itself and does nothing. But it's there in the string, waiting to be found by a regex. ============================================================================ PART 3: THE ENGINE ------------------ This is where it gets weird: s~(\d+)(?{ say qq($1) })~$1-1~e; Let's pull it apart: PIECE WHAT IT DOES ----------- -------------------------------------------- s~~~ Substitution operator (using ~ as delimiter) (\d+) Capture one or more digits into $1 (?{ ... }) Embedded code block - runs during the match say qq($1) Print the captured number $1-1 Replacement: the number minus one e Evaluate replacement as Perl code So this regex: 1. Finds the number (10 on first run) 2. Prints it (via the embedded code block) 3. Replaces it with itself minus one (10 becomes 9) The string in $_ literally changes. After one pass: BEFORE: "10;\ns~..." AFTER: "9;\ns~..." The code is rewriting itself. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 4: THE PAUSE ----------------- sleep 1; Slows it down so you can watch the countdown. Without this, it blasts through in milliseconds. ============================================================================ PART 5: THE LOOP CONDITION -------------------------- $1 ? eval : say q(Countdown complete!); This is a ternary operator doing the work of a while loop. IF $1 IS TRUTHY THEN eval (run $_ again) IF $1 IS FALSY THEN print the goodbye message When does $1 become falsy? When it hits zero. 10 -> truthy -> eval again 9 -> truthy -> eval again ... 1 -> truthy -> eval again 0 -> falsy -> print message and stop No explicit loop counter. No increment. The regex IS the counter. ============================================================================ PART 6: THE KICKOFF ------------------- eval; Outside the heredoc, this single statement starts everything. eval with no argument evaluates $_. So it runs our heredoc code. That code modifies itself and calls eval again. Which runs the modified code. Which modifies itself and calls eval again. Turtles all the way down. ============================================================================ PART 7: THE FULL WALKTHROUGH ---------------------------- Here's what happens step by step: ITERATION $_ CONTAINS $1 ACTION --------- ------------ --- ------------------------- 1 "10;..." 10 Print 10, replace with 9 2 "9;..." 9 Print 9, replace with 8 3 "8;..." 8 Print 8, replace with 7 4 "7;..." 7 Print 7, replace with 6 5 "6;..." 6 Print 6, replace with 5 6 "5;..." 5 Print 5, replace with 4 7 "4;..." 4 Print 4, replace with 3 8 "3;..." 3 Print 3, replace with 2 9 "2;..." 2 Print 2, replace with 1 10 "1;..." 1 Print 1, replace with 0 11 "0;..." 0 $1 is falsy, print goodbye The string transforms on every pass. The code literally changes between executions. ============================================================================ PART 8: THE ANNOTATED VERSION ----------------------------- #!/usr/bin/env perl use feature qw|say|; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Store code-as-data in $_ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ $_ = <<'EOF'; 10; s~(\d+)(?{ say qq($1) })~$1-1~e; sleep 1; $1 ? eval : say q(Countdown complete!); EOF # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Single eval kicks off the chain # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # eval with no args runs $_ # The code in $_ modifies itself and calls eval again # This continues until $1 becomes 0 eval; ============================================================================ PART 9: WHY WOULD YOU DO THIS ----------------------------- You wouldn't. Not in production. But it demonstrates some powerful Perl concepts: * Code is data (heredoc as executable string) * Data is code (eval turns strings into actions) * Regex can execute code (embedded code blocks) * Regex can do math (the /e modifier) * Self-modification is possible (and terrifying) This is the kind of thing that makes Perl both loved and feared. The language doesn't stop you from doing weird stuff. It hands you a chainsaw and says "have fun." ============================================================================ PART 10: VARIATIONS ------------------- Count up instead of down: $_ = <<'EOF'; 1; s~(\d+)(?{ say qq($1) })~$1+1~e; sleep 1; $1 < 10 ? eval : say q(Done!); EOF eval; Fibonacci sequence: $_ = <<'EOF'; 1 1; s~(\d+)\s+(\d+)(?{ say $2 })~$2 . q( ) . ($1+$2)~e; sleep 1; $2 < 100 ? eval : say q(Done!); EOF eval; The pattern is always the same: code that rewrites itself, then evals itself, until some condition stops the recursion. ============================================================================ THE TAKEAWAY ------------ Perl treats code and data as interchangeable. A string can become a program. A program can rewrite itself mid-execution. The regex engine can run arbitrary code while matching. Is this practical? Rarely. Is it powerful? Absolutely. Is it Perl? Hell yes. ============================================================================ . /'\ / \ / \ / _ \ / (_) \ /___________\ | | | | _| |_ |_____| "Code that writes code" ============================================================================ japh.codes