Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Unlocking the Dark Mysteries of Acme::Bleach

by Anonymous Monk
on Jun 29, 2003 at 21:08 UTC ( #270023=perlquestion: print w/ replies, xml ) Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Okay,

I have decided to try to figure out Acme::Bleach. It's only 13 lines of code. But that's 13 lines of Conway's code. I have also looked at similiar modules, such as Acme::Buffy, Acme::Buckaroo, and others, but I want to stick with Acme::Bleach. There's just something about Conway's coding style. Anyway, this is what I know about what the module does: Acme::Bleach encodes/decodes the source of the program. But *how* it does this leaves me a little confused. Let's go through this line by line:

1: package Acme::Bleach; 2: $VERSION = '1.12'; 3: my $tie = " \t"x8; 4:sub whiten { local $_ = unpack "b*", pop; tr/01/ \t/; s/(.{9})/$1\n/ +g; $tie.$_ } 5: sub brighten { local $_ = pop; s/^$tie|[^ \t]//g; tr/ \t/01/; pack +"b*", $_ } 6: sub dirty { $_[0] =~ /\S/ } 7: sub dress { $_[0] =~ /^$tie/ } 8: open 0 or print "Can't rebleach '$0'\n" and exit; 9: (my $shirt = join "", <0>) =~ s/.*^\s*use\s+Acme::Bleach\s*;\n//sm; 10: local $SIG{__WARN__} = \&dirty; 11: do {eval brighten $shirt; exit} unless dirty $shirt && not dress $ +shirt; 12: open 0, ">$0" or print "Cannot bleach '$0'\n" and exit; 13: print {0} "use Acme::Bleach;\n", whiten $shirt and exit;

Basically, Conway has 4 subroutines (whiten, brighten, dirty, dress) in lines 4 - 7. These subroutines appear to do the encoding/decoding, right? Why does he use "local" so much? Anyway, the names are interesting: I assume that whiten would bleach the code, that is, turn the code into white space and tabs, etc. . . But what about brighten? It sounds like they would do the same thing. However, whiten sounds like it would do the bleaching (encoding) and brighten would do the decoding, right? I base these assumptions on the next lines.

In lines 8 and 9, the current script is opened and everything up to and including "use Acme::Bleach" line is omitted from the $shirt variable. That's easy to figure out. Now I note that in line 8 the script is opened for *reading*. I conclude that the source code at this point is not being encoded since nothing yet is being written to the file. I don't understand line 10. He seems to be catching a warning signal for some reason. Why?

Line 11 seems crucial: the content of $shirt is evaluated (i.e, executed) if the source code is not dirty (bleached or whitened). But why does he also test if the $shirt is dressed? I don't see the purpose of the dress subroutine. The dirty subroutine is used to test if the source code has been bleached or not. So why does he need to have another subroutine for? If the dirty subroutine tests if the whitening (encoding) has occurred, then maybe the dress subroutine tests if the code has been brightened (decoded)? I am not sure . . .

I guess most of my confusion stems from my lack of knowledge concerning the pack, unpack, local, pop, translate functions. For example, I have never seen someone use tr/01/ \t/ before as Conway does in the whiten subroutine.

At first, the final lines seem pretty transparent. But I find these also bewildering. I believe that in lines 12 and 13, the source code is encoded (bleached), BUT why does he print "use Acme::Bleach" before he whitens? Yes, I recall that "use Acme::Bleach" is omitted from the $shirt variable, but how come having this use statement is necessary for your code to execute twice? I noticed that if I omitt or otherwise modify this line, then the code doesn't work.

The flow of this program escapes me. I have a strong feeling that a flow chart of this program would be very enlightening. The first time the module is called, the source code is bleached, but the next time the code is called the source code is brightened (decoded). But HOW can your program know to use Conway's module after your program has been bleached? Shouldn't line 13 print the perl hash line before the use statement?

Comment on Unlocking the Dark Mysteries of Acme::Bleach
Select or Download Code
Re: Unlocking the Dark Mysteries of Acme::Bleach
by demerphq (Chancellor) on Jun 29, 2003 at 21:36 UTC

    Line 11 seems crucial: the content of $shirt is evaluated (i.e, executed) if the source code is not dirty (bleached or whitened). But why does he also test if the $shirt is dressed? I don't see the purpose of the dress subroutine.

    Im pretty sure the dress routine checks to see if a header value ($tie) has be prefixed to the data following the use statement. (Note the $tie.$_ as the last statement of whiten()) Its used to determine if the use Acme::Bleach; is at the top of file that needs to be bleached and then printed out, or if its at the top of a file that needs to be brightened (as in its already been whitened) and then executed.

    dirty() tells him if the text below the use line contains non spaces (thus indicating its not been compressed), and dress() tells if the data contains the header value. If the data is both dirty() and not dress()ed then it cant have been bleached already.

    Why does he use "local" so much?

    Because hes messing around with $_ and regexes. Thus its easier to say

    sub foo { local $_=shift; s/.../.../g; }

    than to say

    sub foo { my $str=shift; $str=~s/.../.../g; }

    I believe that in lines 12 and 13, the source code is encoded (bleached), BUT why does he print "use Acme::Bleach" before he whitens?

    As I mentioned before the use line cause both compression and decompression. So of course it has to be output. Furthermore if he output the use line encoded perl would just ignore the whitespace.

    BTW: I personally think it pays to decompress his code a bit

    package Acme::Bleach; $VERSION = '1.12'; my $tie = " \t" x 8; sub whiten { local $_ = unpack "b*", pop; tr/01/ \t/; s/(.{9})/$1\n/g; $tie . $_; } sub brighten { local $_ = pop; s/^$tie|[^ \t]//g; tr/ \t/01/; pack "b*", $_; } sub dirty { $_[0] =~ /\S/; } sub dress { $_[0] =~ /^$tie/; } open 0 or print "Can't rebleach '$0'\n" and exit; ( my $shirt = join "", <0> ) =~ s/.*^\s*use\s+Acme::Bleach\s*;\n//sm; local $SIG{__WARN__} = \&dirty; do { eval brighten $shirt; exit } unless dirty $shirt && not dress $shirt; open 0, ">$0" or print "Cannot bleach '$0'\n" and exit; print {0} "use Acme::Bleach;\n", whiten $shirt and exit;

    HTH


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
      I believe that in lines 12 and 13, the source code is encoded (bleached), BUT why does he print "use Acme::Bleach" before he whitens?

      As I mentioned before the use line cause both compression and decompression. So of course it has to be output. Furthermore if he output the use line encoded perl would just ignore the whitespace.

      What? How can a Perl script be executed without the hash line (#!/usr/bin/perl)? I sorta understand the first bleaching, but how can the script work on subsequent occasions without the hash line?!?

        Blink. Blink. Blink.

        What? How can a Perl script be executed without the hash line (#!/usr/bin/perl)? I sorta understand the first bleaching, but how can the script work on subsequent occasions without the hash line?!?

        Where to start. Ok, at the Acme::Bleach level, he doesnt care. The file hes produced, only will ever make sense if perl itself reads it and runs it. So the presence or absence of the shebange line is neither here nor there. Its up to the user to make that happen as far as hes concerned. (Although the code is patchable to preserve any existing shebang line, perhaps you should prepare a patch?)

        Second, of course its possible for the script to be executed without a shebang line. perl script.pl or perl script.pm works perfectly fine, and on many of the OS's that Perl (and Acme::Bleach) run on shebang lines dont mean anything at all (except that Perl itself pretends like they do). On Win32 for instance file associations are via the registry.

        I think he expects the line to be the top one, (I've not read the docs, only the code you posted), and that if you want a shebang line or anything else used first that you'll put it in after the use Acme::Bleach does the encode.

        But I dont think you should take the failings of the module too seriously, I dont think he took any of it seriously :-)


        ---
        demerphq

        <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
      If eloquent code is the mark of a master programmer, Conway has proven himself....a hack.
        Acme::Bleach is elegant, not eloquent. Reading it is, to me, like tasting a fine wine. It's like golfing. Do you put golfed code into production? No. Is it neat to play with? Yes. Don't confuse items meant for one as items meant for the other. Eloquence isn't always the goal.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

        Ah, now if I could be but a 10th of the hacker that he is....


        ---
        demerphq

        <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: Unlocking the Dark Mysteries of Acme::Bleach
by broquaint (Abbot) on Jun 29, 2003 at 22:05 UTC
    Let's break it down then
    open 0 or print "Can't rebleach '$0'\n" and exit;
    Open the filename name stored in the global variable $0 (which stores the name of the script perl was called with) and assign a filehandle to 0 (this is a rather obscure feature of open). If it can't be opened then print out an error message and exit the program.
    (my $shirt = join "", <0>) =~ s/.*^\s*use\s+Acme::Bleach\s*;\n//sm;
    Slurp in the contents of the 0 filehandle (the program code), join them into a single string and assign them to $shirt, then immediately perform a replace on it by removing everything before and including use Acme::Bleach.
    local $SIG{__WARN__} = \&dirty;
    Locally change the __WARN__ handler to dirty() which returns true if a warning matches non-whitespace (likely to supress any warnings in the code that follows).
    do { eval brighten $shirt; exit } unless dirty $shirt && not dress $shirt;
    Unless the contents of $shirt (the program code) matches non-whitespace or doesn't begin with $tie (a sequence of 8 space and tab alternations) then evaluate the return of brighten $shirt and exit (this is the bit that is run post-bleaching).
    sub brighten { local $_ = pop; s/^$tie|[^ \t]//g; tr/ \t/01/; pack "b*", $_ }
    Locally assign $_ to the last element of @_, which is the program code (the is $_ localised so it isn't clobbered and also sets it as the current topicalizer). Remove a leading $tie (the tabs) or any non space/tab characters. The tr will change every space to 0 and every tab to 1 and the pack then converts it back to it's original form (ready to be evaluated).
    open 0, ">$0" or print "Cannot bleach '$0'\n" and exit;
    Reopen the the script in $0 for writing (to be bleached in this case).
    print {0} "use Acme::Bleach;\n", whiten $shirt and exit;
    Use the alternate syntax of print to write use Acme::Bleach; and the return of whiten $shirt to the 0 filehandle then exit.
    sub whiten { local $_ = unpack "b*", pop; tr/01/ \t/; s/(.{9})/$1\n/g; $tie.$_ }
    Once again, $_ is localised and is assigned a bit string created by unpack of the last element of @_ (the contents of $0 in this case). Replace all the 0s and 1s as spaces and tabs respectively and return the modified $_ with $tie prepended.

    So basically Acme::Bleach converts the contents of the code into a bitstring of spaces and tabs on the first run, and then on subsequent runs uncompresses the bitstring and runs the code. Simple as that :)
    HTH

    _________
    broquaint

    update: $tie comment now correct and further explanation of the warning handler localisation

      or doesn't begin with $tie (a sequence of 8 tabs)

      Actually if you look closely its actually space-tab x 8. :-) I made the same mistake at first too. Its kinda cute because it represents '01' x 8. Which is longer than any of the subsequent encoded lines, which are all at most 9 chars + /n.

      my $tie = " \t"x8;

      Locally change the __WARN__ handler to dirty() which returns true if a warning matches non-whitespace.

      The point is why though. I beleive the intention is suppress any warnings generated by the following code, regardless as to whether it actually includes a use warnings or what not. Of course if __WARN__ is overriden later theres a problem.. :-)


      ---
      demerphq

      <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
        The return value of a signal handler is insignificant. Since the function doesn't have any side effects, he's effectively just ignoring warnings. It's almost obvious he's simply doing it for (Perl) poetic value.

        Makeshifts last the longest.

Re: Unlocking the Dark Mysteries of Acme::Bleach
by Beatnik (Parson) on Jun 30, 2003 at 09:02 UTC
    I covered Acme::Bleach at YAPC::Eu last year... Slides and text are here

    Greetz
    Beatnik
    ... I'm belgian but I don't play one on TV.
Re: Unlocking the Dark Mysteries of Acme::Bleach
by tachyon (Chancellor) on Jun 30, 2003 at 11:02 UTC

    unbleach.pl :-)

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: Unlocking the Dark Mysteries of Acme::Bleach
by Anonymous Monk on Jul 01, 2003 at 03:19 UTC
    Thanks everyone. I have a much better understanding of this module. I am also going to read up on Beatnik's presentation. Thanks again.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://270023]
Approved by phydeauxarff
Front-paged by gmax
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (8)
As of 2014-07-28 04:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (185 votes), past polls