package Acme::EyeDrops; require 5.005_03; use strict; use vars qw($VERSION @ISA @EXPORT_OK); require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw(ascii_to_sightly sightly_to_ascii regex_print_sightly regex_eval_sightly clean_print_sightly clean_eval_sightly regex_binmode_print_sightly clean_binmode_print_sightly get_eye_dir get_eye_shapes get_eye_string get_builtin_shapes get_eye_properties get_eye_keywords find_eye_shapes make_triangle make_siertri make_banner border_shape invert_shape reflect_shape rotate_shape reduce_shape expand_shape hjoin_shapes pour_text pour_sightly sightly); $VERSION = '1.55'; { # This table was generated by demo/gentable.pl. my @C = ( q Z('!'^'!')Z,q Z('('^')')Z,q Z('<'^'>')Z,q Z('>'^'=')Z, q Z('>'^':')Z,q Z('>'^';')Z,q Z('+'^'-')Z,q Z('*'^'-')Z, q Z('+'^'#')Z,q Z('*'^'#')Z,q Z('!'^'+')Z,q Z('!'^'*')Z, q Z('!'^'-')Z,q Z('!'^',')Z,q Z('!'^'/')Z,q Z('!'^'.')Z, q Z('?'^'/')Z,q Z('<'^'-')Z,q Z('-'^'?')Z,q Z('.'^'=')Z, q Z('+'^'?')Z,q Z('*'^'?')Z,q Z('?'^')')Z,q Z('<'^'+')Z, q Z('%'^'=')Z,q Z('&'^'?')Z,q Z('?'^'%')Z,q Z('>'^'%')Z, q Z('&'^':')Z,q Z('<'^'!')Z,q Z('?'^'!')Z,q Z('%'^':')Z, q Z('{'^'[')Z,q Z'!'Z,q Z'\\\\'.'"'Z,q Z'#'Z, q Z'\\\\'.'$'Z,q Z'%'Z,q Z'&'Z,q Z"'"Z,q Z'('Z,q Z')'Z, q Z'*'Z,q Z'+'Z,q Z','Z,q Z'-'Z,q Z'.'Z,q Z'/'Z, q Z('^'^('`'|'.'))Z,q Z('^'^('`'|'/'))Z,q Z('^'^('`'|','))Z, q Z('^'^('`'|'-'))Z,q Z('^'^('`'|'*'))Z,q Z('^'^('`'|'+'))Z, q Z('^'^('`'|'('))Z,q Z('^'^('`'|')'))Z,q Z(':'&'=')Z, q Z(';'&'=')Z,q Z':'Z,q Z';'Z,q Z'<'Z,q Z'='Z,q Z'>'Z,q Z'?'Z, q Z'\\\\'.'@'Z,q Z('`'^'!')Z,q Z('`'^'"')Z,q Z('`'^'#')Z, q Z('`'^'$')Z,q Z('`'^'%')Z,q Z('`'^'&')Z,q Z('`'^"'")Z, q Z('`'^'(')Z,q Z('`'^')')Z,q Z('`'^'*')Z,q Z('`'^'+')Z, q Z('`'^',')Z,q Z('`'^'-')Z,q Z('`'^'.')Z,q Z('`'^'/')Z, q Z('{'^'+')Z,q Z('{'^'*')Z,q Z('{'^')')Z,q Z('{'^'(')Z, q Z('{'^'/')Z,q Z('{'^'.')Z,q Z('{'^'-')Z,q Z('{'^',')Z, q Z('{'^'#')Z,q Z('{'^'"')Z,q Z('{'^'!')Z,q Z'['Z, q Z'\\\\'.'\\\\'Z,q Z']'Z,q Z'^'Z,q Z'_'Z, q Z'`'Z,q Z('`'|'!')Z,q Z('`'|'"')Z,q Z('`'|'#')Z, q Z('`'|'$')Z,q Z('`'|'%')Z,q Z('`'|'&')Z,q Z('`'|"'")Z, q Z('`'|'(')Z,q Z('`'|')')Z,q Z('`'|'*')Z,q Z('`'|'+')Z, q Z('`'|',')Z,q Z('`'|'-')Z,q Z('`'|'.')Z,q Z('`'|'/')Z, q Z('['^'+')Z,q Z('['^'*')Z,q Z('['^')')Z,q Z('['^'(')Z, q Z('['^'/')Z,q Z('['^'.')Z,q Z('['^'-')Z,q Z('['^',')Z, q Z('['^'#')Z,q Z('['^'"')Z,q Z('['^'!')Z,q Z'\\\\'.'{'Z, q Z'|'Z,q Z'\\\\'.'}'Z,q Z'~'Z,q Z('!'^'^')Z ); push @C, map(join('.', q#'\\\\'#, $C[120], map($C[$_], unpack('C*', sprintf('%x', $_)))), 128..255); sub ascii_to_sightly { join '.', map($C[$_], unpack('C*', $_[0])) } } sub sightly_to_ascii { eval eval q#'"'.# . $_[0] . q#.'"'# } sub regex_print_sightly { q#''=~('('.'?'.'{'.# . ascii_to_sightly('print') . q#.'"'.# . &ascii_to_sightly . q#.'"'.'}'.')')#; } sub regex_binmode_print_sightly { q#''=~('('.'?'.'{'.# . ascii_to_sightly('binmode(STDOUT);print') . q#.'"'.# . &ascii_to_sightly . q#.'"'.'}'.')')#; } sub regex_eval_sightly { q#''=~('('.'?'.'{'.# . ascii_to_sightly('eval') . q#.'"'.# . &ascii_to_sightly . q#.'"'.'}'.')')#; } sub clean_print_sightly { qq#print eval '"'.\n\n\n# . &ascii_to_sightly . q#.'"'#; } sub clean_binmode_print_sightly { qq#binmode(STDOUT);print eval '"'.\n\n\n# . &ascii_to_sightly . q#.'"'#; } sub clean_eval_sightly { qq#eval eval '"'.\n\n\n# . &ascii_to_sightly . q#.'"'#; } # ----------------------------------------------------------------- sub _slurp_tfile { my $f = shift; local *T; open(T, $f) or die "open '$f': $!"; local $/; my $s = ; close(T); $s; } # Poor man's properties (see also YAML, java.util.Properties). # Return ref to property hash. sub _get_properties { my $f = shift; local *F; open(F, $f) or die "open '$f': $!"; my $l; my %h; while (defined($l = )) { chomp($l); if ($l =~ s/\\$//) { my $n = ; $n =~ s/^\s+//; $l .= $n; redo unless eof(F); } $l =~ s/^\s+//; $l =~ s/\s+$//; next unless length($l); next if $l =~ /^#/; my ($k, $v) = split(/\s*:\s*/, $l, 2); $h{$k} = $v; } close(F); return \%h; } sub _def_ihandler { print STDERR $_[0] } # Return largest no. of tokens with total length less than $slen ($slen > 0). sub _guess_ntok { my ($rtok, $sidx, $slen, $rexact) = @_; my $tlen = 0; for my $i ($sidx .. $sidx + $slen) { ($tlen += length($rtok->[$i])) < $slen or return $i - $sidx + (${$rexact} = $tlen == $slen); } # should never get here } sub _guess_compact_ntok { my ($rtok, $sidx, $slen, $rexact, $fcompact) = @_; my $tlen = 0; for my $i ($sidx .. $sidx + $slen + $slen) { ($tlen += length($rtok->[$i]) - ($i > $sidx+1 && $rtok->[$i-1] eq '.' && substr($rtok->[$i], 0, 1) eq "'" && substr($rtok->[$i-2], 0, 1) eq "'" ? (${$fcompact} = 3) : 0)) < $slen or return $i - $sidx + ($tlen > $slen ? 0 : (${$rexact} = 1) + ($i > $sidx && $rtok->[$i] eq '.' && substr($rtok->[$i-1], 0, 1) eq "'" && $rtok->[$i+1] =~ /^'..$/ ? (${$fcompact} = 1) : 0)); } # should never get here } sub _compact_join { my ($rtok, $sidx, $n) = @_; my $s = ""; for my $i ($sidx .. $sidx + $n - 1) { if ($i > $sidx+1 && $rtok->[$i-1] eq '.' && substr($rtok->[$i], 0, 1) eq "'" && substr($rtok->[$i-2], 0, 1) eq "'") { substr($s, -2) = substr($rtok->[$i], 1); # 'a'.'b' to 'ab' } else { $s .= $rtok->[$i]; } } $s; } # Pour $n tokens from @{$rtok} (starting at index $sidx) into string # of length $slen. Return string or undef if unsuccessful. sub _pour_chunk { my ($rtok, $sidx, $n, $slen) = @_; my $eidx = $sidx + $n - 1; my $tlen = 0; my $idot = my $iquote = my $i3quote = my $iparen = my $idollar = -1; for my $i ($sidx .. $eidx) { $tlen += length($rtok->[$i]); if ($rtok->[$i] eq '.') { $idot = $i } elsif ($rtok->[$i] eq '(') { $iparen = $i } elsif (substr($rtok->[$i], 0, 1) eq '$') { $idollar = $i } elsif ($rtok->[$i] =~ /^['"]/) { $iquote = $i; $i3quote = $i if length($rtok->[$i]) == 3; } } die "oops" if $tlen >= $slen; my $i2 = (my $d = $slen - $tlen) >> 1; $idot >= 0 && !($d%3) and return join("", @{$rtok}[$sidx .. $idot-1], ".''" x int($d/3), @{$rtok}[$idot .. $eidx]); if (!($d&1) and $iquote >= 0 || $idollar >= 0) { $iquote = $idollar if $iquote < 0; return join("", @{$rtok}[$sidx .. $iquote-1], '(' x $i2 . $rtok->[$iquote] . ')' x $i2, @{$rtok}[$iquote+1 .. $eidx]); } $i3quote >= 0 and return join("", @{$rtok}[$sidx .. $i3quote-1], $d == 1 ? '"\\' . substr($rtok->[$i3quote], 1, 1) . '"' : '(' x $i2 . '"\\' . substr($rtok->[$i3quote], 1, 1) . '"' . ')' x $i2, @{$rtok}[$i3quote+1 .. $eidx]); return unless $d == 1; $iparen >= 0 and return join("", @{$rtok}[$sidx .. $iparen-1], '+' . $rtok->[$iparen], @{$rtok}[$iparen+1 .. $eidx]); # ouch, can't test for eq '(' in case next chunk also adds '+' $rtok->[$eidx] ne '=' && $rtok->[$sidx+$n] =~ /^['"]/ ? join("", @{$rtok}[$sidx .. $eidx], '+') : undef; } sub _pour_compact_chunk { my ($rtok, $sidx, $n, $slen) = @_; my @mytok; for my $i ($sidx .. $sidx + $n - 1) { if ($i > $sidx+1 && $rtok->[$i-1] eq '.' && substr($rtok->[$i], 0, 1) eq "'" && substr($rtok->[$i-2], 0, 1) eq "'") { pop(@mytok); my $qtok = pop(@mytok); # 'a'.'b' to 'ab' push(@mytok, substr($qtok, 0, -1) . substr($rtok->[$i], 1)); } else { push(@mytok, $rtok->[$i]); } } push(@mytok, $rtok->[$sidx+$n]); # _pour_chunk checks next token _pour_chunk(\@mytok, 0, $#mytok, $slen); } # Pour unsightly text $txt into shape defined by string $tlines. sub pour_text { my ($tlines, $txt, $gap, $tfill) = @_; $txt =~ s/\s+//g; my $ttlen = 0; my $txtend = length($txt); my @tnlines = map(length() ? [map length, split/([^ ]+)/] : undef, split(/\n/, $tlines)); for my $r (grep($_, @tnlines)) { for my $i (0 .. $#{$r}) { $i & 1 and $ttlen += $r->[$i] } } my $nshape = int($txtend/$ttlen); my $rem = $txtend % $ttlen; if ($rem || !$nshape) { ++$nshape; $txt .= $tfill x (int(($ttlen-$rem)/length($tfill))+1) if length($tfill); } my $s = ""; my $p = 0; for (my $n = 1; 1; ++$n, $s .= "\n" x $gap) { for my $r (@tnlines) { if ($r) { for my $i (0 .. $#{$r}) { if ($i & 1) { $s .= substr($txt, $p, $r->[$i]); $p += $r->[$i]; return "$s\n" if !length($tfill) && $p >= $txtend; } else { $s .= ' ' x $r->[$i]; } } } $s .= "\n"; } last if $n >= $nshape; } $s; } # Make filler code to stuff on end of program to fill last shape. sub _make_filler { my $fv = shift; # list reference of filler variables my $nfv = @{$fv}; # Beware with these filler values. # Avoid $; $" ';' (to avoid clash with " and ; in later parsing). # END block is trouble because it is executed after this filler. # Setting $^ or $~ (but not $:) to weird values resets $@. # For example: $~='?'&'!'; (this looks like a Perl bug to me). # For now, just stick with letters and numbers. my @filleqto = ( [ q#'.'#, '^', q^'~'^ ], [ q#'@'#, '|', q^'('^ ], [ q#')'#, '^', q^'['^ ], [ q#'`'#, '|', q^'.'^ ], [ q#'('#, '^', q^'}'^ ], [ q#'`'#, '|', q^'!'^ ], [ q#')'#, '^', q^'}'^ ], [ q#'*'#, '|', q^'`'^ ], [ q#'+'#, '^', q^'_'^ ], [ q#'&'#, '|', q^'@'^ ], [ q#'['#, '&', q^'~'^ ], [ q#','#, '^', q^'|'^ ] ); $nfv > @filleqto and die "too many fv"; my $rem = @filleqto % $nfv; $rem and splice(@filleqto, -$rem); my $v = -1; map(($fv->[++$v % $nfv], '=', @{$_}, ';'), @filleqto); } # Pour sightly program $prog into shape defined by string $tlines. sub pour_sightly { my ($tlines, $prog, $gap, $fillv, $compact, $ihandler) = @_; $ihandler ||= \&_def_ihandler; my $ttlen = 0; my @tnlines = map(length() ? [map length, split/([^ ]+)/] : undef, split(/\n/, $tlines)); for my $r (grep($_, @tnlines)) { for my $i (0 .. $#{$r}) { $i & 1 and $ttlen += $r->[$i] } } my $outstr = ""; my @ptok; if ($prog) { if ($prog =~ /^''=~/g) { push(@ptok, ($tlines =~ /(\S+)/ ? length($1) : 0) == 3 ? "'?'" : "''", '=~'); } elsif ($prog =~ /(.*eval.*\n\n\n)/g) { $outstr .= $1; } push(@ptok, $prog =~ /[().&|^]|'\\\\'|.../g); # ... is "'"|'.' } my $iendprog = @ptok; my @filler = _make_filler(ref($fillv) ? $fillv : [ '$:', '$~', '$^' ]); # Note: 11 is the length of a filler item, for example, $:='.'^'~'; # And there are 6 tokens in each filler item: $: = '.' ^ '~' ; push(@ptok, 'Z', (@filler) x (int($ttlen/(11 * int(@filler / 6))) + 1)); my $sidx = 0; for (my $nshape = 1; 1; ++$nshape, $outstr .= "\n" x $gap) { for my $rline (@tnlines) { unless ($rline) { $outstr .= "\n"; next } for my $it (0 .. $#{$rline}) { unless ($it & 1) {$outstr .= ' ' x $rline->[$it]; next } (my $tlen = $rline->[$it]) == (my $plen = length($ptok[$sidx])) and $outstr .= $ptok[$sidx++], next; if ($plen > $tlen) { $outstr .= '(' x $tlen; splice(@ptok, $sidx+1, 0, (')') x $tlen); $iendprog += $tlen if $sidx < $iendprog; next; } my $fcompact = my $fexact = 0; my $n = $compact ? _guess_compact_ntok(\@ptok, $sidx, $tlen, \$fexact, \$fcompact) : _guess_ntok(\@ptok, $sidx, $tlen, \$fexact); if ($fexact) { $outstr .= $fcompact ? _compact_join(\@ptok, $sidx, $n) : join("", @ptok[$sidx .. $sidx+$n-1]); $sidx += $n; next; } my $str; --$n while $n > 0 && !defined($str = $fcompact ? _pour_compact_chunk(\@ptok, $sidx, $n, $tlen) : _pour_chunk(\@ptok, $sidx, $n, $tlen)); if ($n) { $outstr .= $str; $sidx += $n; next } ++$n while $n < $tlen && length($ptok[$sidx+$n]) < 2; die "oops ($n >= $tlen)" if $n >= $tlen; $outstr .= join("", @ptok[$sidx .. $sidx+$n-1]); $sidx += $n; $outstr .= '(' x (my $nleft = $tlen - $n); splice(@ptok, $sidx+1, 0, (')') x $nleft); $iendprog += $nleft if $sidx < $iendprog; } $outstr .= "\n"; } $ihandler->("$nshape shapes completed.\n"); last if $sidx >= $iendprog; } my $eidx = rindex($outstr, 'Z'); substr($outstr, $eidx, 1) = ';' if $eidx >= 0; return $outstr if $sidx == $iendprog || $sidx == $iendprog+1; die "oops" if $eidx < 0; ref($fillv) or return substr($outstr, 0, $eidx) . (length($fillv) ? pour_text(substr($outstr, $eidx), "", 0, $fillv) : "\n"); (my $idx = rindex($outstr, ';')) >= 0 or return $outstr; my @t = substr($outstr, $idx+1) =~ /[()&|^=;]|\$.|'[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"/g or return $outstr; my $nl = my $nr = my $ne = 0; for my $c (@t) { if ($c eq '(') {++$nl} elsif ($c eq ')') {++$nr} elsif ($c eq '=') {++$ne} } if ($ne == 0 || $nl != $nr || $t[-1] eq '=') { my $f = ';'; # Trouble: wipe out last bit with filler for my $i ($idx+1 .. length($outstr)-2) { substr($outstr, $i, 1) =~ tr/ \n// or substr($outstr, $i, 1) = $f = $f eq '#' ? ';' : '#'; } } elsif ($t[-1] eq '|' or $t[-1] eq '^' or $t[-1] eq '&') { $outstr =~ s/\S(\s*)$/;$1/; } $outstr; } # ----------------------------------------------------------------- sub _border { my ($a, $w, $c, $l, $r, $t, $b) = @_; my $z = $c x ($w+$l+$r); my $f = $c x $l; my $g = $c x $r; for (@{$a}) { $_ = $f . $_ . $g } unshift(@{$a}, ($z) x $t); push(@{$a}, ($z) x $b); } sub border_shape { my ($tlines, $gl, $gr, $gt, $gb, $wl, $wr, $wt, $wb) = @_; my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0; for my $l (@a) { $m = length($l) if length($l) > $m } for my $l (@a) { $l .= ' ' x ($m - length($l)) } $gl || $gr || $gt || $gb and _border(\@a, $m, ' ', $gl, $gr, $gt, $gb); $wl || $wr || $wt || $wb and _border(\@a, $m+$gl+$gr,'#',$wl,$wr,$wt,$wb); join("\n", @a, ""); } sub invert_shape { my $tlines = shift; my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0; for my $l (@a) { $m = length($l) if length($l) > $m } for my $l (@a) { $l .= ' ' x ($m - length($l)) } my $s = join("\n", @a, ""); $s =~ tr/ #/# /; $s =~ s/ +$//mg; $s; } sub reflect_shape { my $tlines = shift; my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0; for my $l (@a) { $m = length($l) if length($l) > $m } my $s = join("\n", map(scalar reverse($_ . ' ' x ($m - length)), @a), ""); $s =~ s/ +$//mg; $s; } sub hjoin_shapes { my ($g, @shapes) = @_; my $ml = 0; my @lines; for my $s (@shapes) { my $n = $s =~ tr/\n//; $ml = $n if $n > $ml } for my $tlines (@shapes) { my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0; for my $l (@a) { $m = length($l) if length($l) > $m } for my $l (@a) { $l .= ' ' x ($m - length($l) + $g) } push(@a, (' ' x ($m + $g)) x ($ml - @a)); for my $i (0..$#a) { $lines[$i] .= $a[$i] } } my $s = join("\n", @lines, ""); $s =~ s/ +$//mg; $s; } sub reduce_shape { my ($tlines, $f) = @_; my $i = $f++; my $s = ""; for my $l (grep(!(++$i%$f), split(/\n/, $tlines))) { for ($i = 0; $i < length($l); $i += $f) { $s .= substr($l, $i, 1) } $s .= "\n"; } $s =~ s/ +$//mg; $s; } sub expand_shape { my ($s, $f) = @_; my $i = ' ' x ++$f; my $j = '#' x $f; $s =~ s/ /$i/g; $s =~ s/#/$j/g; my $t = ""; for my $l (split(/^/, $s, -1)) { $t .= $l x $f } $t; } # Rotate shape clockwise: 90, 180 or 270 degrees # (other angles are left as an exercise for the reader:-) sub rotate_shape { my ($tlines, $degrees, $rtype, $flip) = @_; $degrees == 180 and return join("\n", reverse(split(/\n/, $tlines)), ""); my $t = $rtype==0 ? 2 : 1; my $inc = $rtype==1 ? 2 : 1; my @a = split(/^/, $tlines, -1); chop(@a); my $m = 0; my $s = ""; for my $l (@a) { $m = length($l) if length($l) > $m } for my $l (@a) { $l .= ' ' x ($m - length($l)) } if ($degrees == 90) { @a = reverse(@a) unless $flip; for (my $i = 0; $i < $m; $i += $inc) { for (@a) {$s .= substr($_, $i, 1) x $t} $s .= "\n" } } elsif ($degrees == 270) { @a = reverse(@a) if $flip; for (my $i = $m-1; $i >= 0; $i -= $inc) { for (@a) {$s .= substr($_, $i, 1) x $t} $s .= "\n" } } $s =~ s/ +$//mg; $s; } sub make_triangle { my $w = shift; $w & 1 or ++$w; $w < 9 and $w = 9; my $n = $w >> 1; my $s; for (my $i=1;$i<=$w;$i+=2) { $s .= ' ' x $n-- . '#' x $i . "\n" } $s; } sub make_siertri { my $w = shift; $w < 3 and $w = 5; my $n = 2 ** $w; my $s; for my $i (0 .. $n-1) { --$n; $s .= ' ' x $n . join('', map($n & $_ ? ' ' : '##', 0 .. $i)) . "\n"; } $s; } sub make_banner { my ($w, $src) = @_; # Linux /usr/games/banner can be used. # CPAN Text::Banner will hopefully be enhanced so it can be used too. my $b_exe = '/usr/games/banner'; -x $b_exe or die "'$b_exe' not available on this platform."; my $f = $w ? "-w $w" : ""; $src =~ s/\s+/ /g; $src =~ s/ $//; # Following characters not in /usr/games/banner character set: # \ [ ] { } < > ^ _ | ~ # Also must escape ' from the shell. $src =~ tr#_\\[]{}<>^|~'`#-/()()()H!T""#; my $s = ""; my $len = length($src); for (my $i = 0; $i < $len; $i += 512) { my $cmd = "$b_exe $f '" . substr($src, $i, 512) . "'"; $s .= `$cmd`; my $rc = $? >> 8; $rc and die "<$cmd>: rc=$rc"; } $s =~ s/\s+$/\n/; $s =~ s/ +$//mg; # Remove as many leading spaces as possible. my $m = 32000; # regex /^ {$m}/ blows up if $m > 32766 while ($s =~ /^( *)\S/mg) { $m = length($1) if length($1) < $m } $s =~ s/^ {$m}//mg if $m; $s; } # ------------------------------------------------------------------------- sub _bi_all { join "\n" x $_[0]->{Width}, map(_get_eye_string($_[0]->{EyeDir}, $_), _get_eye_shapes($_[0]->{EyeDir})) } sub _bi_triangle { make_triangle($_[0]->{Width}) } sub _bi_siertri { make_siertri($_[0]->{Width}) } sub _bi_banner { make_banner($_[0]->{Width}, $_[0]->{BannerString}) } sub _bi_srcbanner { make_banner($_[0]->{Width}, $_[0]->{SourceString}) } { my %builtin_shapes = ( 'all' => \&_bi_all, 'triangle' => \&_bi_triangle, 'siertri' => \&_bi_siertri, 'banner' => \&_bi_banner, 'srcbanner' => \&_bi_srcbanner ); sub get_builtin_shapes { sort keys %builtin_shapes } # Return built-in shape string or undef if invalid shape. sub _get_builtin_string { my $shape = shift; return unless exists($builtin_shapes{$shape}); $builtin_shapes{$shape}->(shift); } } sub sightly { my $ruarg = shift; my %arg = ( Shape => "", ShapeString => "", SourceFile => "", SourceString => "", SourceHandle => undef, InformHandler => undef, Width => 0, BannerString => "", Text => 0, TextFiller => "", Regex => 0, Compact => 0, Print => 0, Binary => 0, Gap => 0, Rotate => 0, RotateType => 0, RotateFlip => 0, Reflect => 0, Reduce => 0, Expand => 0, Invert => 0, TrailingSpaces => 0, RemoveNewlines => 0, Indent => 0, BorderGap => 0, BorderGapLeft => 0, BorderGapRight => 0, BorderGapTop => 0, BorderGapBottom => 0, BorderWidth => 0, BorderWidthLeft => 0, BorderWidthRight => 0, BorderWidthTop => 0, BorderWidthBottom => 0, TrapEvalDie => 0, TrapWarn => 0, FillerVar => [], EyeDir => get_eye_dir() ); for my $k (keys %{$ruarg}) { exists($arg{$k}) or die "invalid parameter '$k'"; $arg{$k} = $ruarg->{$k}; } length($arg{SourceFile}) && $arg{SourceHandle} and die "cannot specify both SourceFile and SourceHandle"; length($arg{SourceFile}) && length($arg{SourceString}) and die "cannot specify both SourceFile and SourceString"; length($arg{SourceString}) && $arg{SourceHandle} and die "cannot specify both SourceString and SourceHandle"; $arg{Shape} && $arg{ShapeString} and die "cannot specify both Shape and ShapeString"; if (length($arg{SourceFile})) { local $/; local *S; open(S, $arg{SourceFile}) or die "open '$arg{SourceFile}': $!"; binmode(S) if $arg{Binary}; $arg{SourceString} = ; close(S); } elsif ($arg{SourceHandle}) { # Using readline() fails with perl 5.005. # local $/; $arg{SourceString} = readline($arg{SourceHandle}); local $/; my $h = $arg{SourceHandle}; $arg{SourceString} = <$h>; } my $fill = $arg{FillerVar}; if (ref($fill) && !$arg{Text}) { # Non-rigourous check for module (package) or END block. @{$fill} or $fill = ($arg{SourceString} =~ /^\s*END\b/m or $arg{SourceString} =~ /^\s*package\b/m) ? [ '$:', '$~', '$^' ] : [ '$:', '$~', '$^', '$/', '$,', '$\\' ]; } $arg{RemoveNewlines} and $arg{SourceString} =~ tr/\n//d; my $shape = my $sightly = ""; length($arg{SourceString}) && !$arg{Text} and $sightly = $arg{Print} ? ( $arg{Regex} ? ( $arg{Binary} ? regex_binmode_print_sightly($arg{SourceString}) : regex_print_sightly($arg{SourceString}) ) : ( $arg{Binary} ? clean_binmode_print_sightly($arg{SourceString}) : clean_print_sightly($arg{SourceString}) ) ) : ( $arg{Regex} ? regex_eval_sightly($arg{SourceString}) : clean_eval_sightly($arg{SourceString}) ); if ($arg{ShapeString}) { $shape = $arg{ShapeString}; } elsif ($arg{Shape}) { $shape = join("\n" x $arg{Gap}, map(_get_builtin_string($_, \%arg) || (m#[./]# ? _slurp_tfile($_) : _get_eye_string($arg{EyeDir}, $_)), split(/,/, $arg{Shape}))); } elsif ($arg{Width}) { die "invalid width $arg{Width} (must be > 3)" if !$arg{Text} && $arg{Width} < 4; $shape = '#' x $arg{Width}; } $shape or return $sightly; $arg{Rotate} and $shape = rotate_shape($shape, $arg{Rotate}, $arg{RotateType}, $arg{RotateFlip}); $arg{Reflect} and $shape = reflect_shape($shape); $arg{Reduce} and $shape = reduce_shape($shape, $arg{Reduce}); $arg{Expand} and $shape = expand_shape($shape, $arg{Expand}); $arg{Invert} and $shape = invert_shape($shape); $arg{TrailingSpaces} || $arg{BorderGap} || $arg{BorderWidth} || $arg{BorderGapLeft} || $arg{BorderWidthLeft} || $arg{BorderGapRight} || $arg{BorderWidthRight} || $arg{BorderGapTop} || $arg{BorderWidthTop} || $arg{BorderGapBottom} || $arg{BorderWidthBottom} and $shape = border_shape($shape, $arg{BorderGapLeft} || $arg{BorderGap}, $arg{BorderGapRight} || $arg{BorderGap}, $arg{BorderGapTop} || $arg{BorderGap}, $arg{BorderGapBottom} || $arg{BorderGap}, $arg{BorderWidthLeft} || $arg{BorderWidth}, $arg{BorderWidthRight} || $arg{BorderWidth}, $arg{BorderWidthTop} || $arg{BorderWidth}, $arg{BorderWidthBottom} || $arg{BorderWidth}); if ($arg{Indent}) { my $s = ' ' x $arg{Indent}; $shape =~ s/^/$s/mg } $arg{Text} and return pour_text($shape, $arg{SourceString}, $arg{Gap}, $arg{TextFiller}); 'local $SIG{__WARN__}=sub{};' x $arg{TrapWarn} . pour_sightly($shape, $sightly, $arg{Gap}, $fill, $arg{Compact}, $arg{InformHandler}) . "\n\n\n;die \$\@ if \$\@\n" x $arg{TrapEvalDie}; } # ------------------------------------------------------------------------- sub _get_eye_shapes { my $d = shift; local *D; opendir(D, $d) or die "opendir '$d': $!"; my @e = sort map(/(.+)\.eye$/, readdir(D)); closedir(D); @e; } sub _get_eye_string { _slurp_tfile($_[0] . '/' . $_[1] . '.eye') } sub _get_eye_properties { my $f = $_[0] . '/' . $_[1] . '.eyp'; -f $f or return; _get_properties($f); } sub _get_eye_keywords { my $d = shift; my %h; SHAPE: for my $s (_get_eye_shapes($d)) { my $p = _get_eye_properties($d, $s) or next SHAPE; # no properties exists($p->{keywords}) or next SHAPE; # no keywords property my @k = split(" ", $p->{keywords}) or next SHAPE; # no keywords for my $k (@k) { push(@{$h{$k}}, $s) } } return \%h; } sub _find_eye_shapes { my $d = shift; @_ or die "oops, no keywords given"; my @skey = map([split/\s+OR\s+/], @_); my @ret; SHAPE: for my $s (_get_eye_shapes($d)) { my $p = _get_eye_properties($d, $s) or next SHAPE; # no properties exists($p->{keywords}) or next SHAPE; # no keywords property my @k = split(" ", $p->{keywords}) or next SHAPE; # no keywords my %h; @h{@k} = (); for my $k (@skey) { # XXX: short-circuiting List::Util::first() better than grep here. grep(exists($h{$_}), @{$k}) or next SHAPE; # AND, all must be true } push(@ret, $s); } return @ret; } sub get_eye_shapes { _get_eye_shapes(get_eye_dir()) } sub get_eye_string { _get_eye_string(get_eye_dir(), shift) } sub get_eye_properties { _get_eye_properties(get_eye_dir(), shift) } sub get_eye_keywords { _get_eye_keywords(get_eye_dir()) } sub find_eye_shapes { _find_eye_shapes(get_eye_dir(), @_) } # $eye_dir is the directory containing the .eye file shapes. # Note: $eye_dir is only eval-hostile line in EyeDrops.pm; do not change it # for t/19_surrounds.t and "EyeDropping EyeDrops.pm" section of doco relies # on it. Remove ".pm" from "...Acme/EyeDrops.pm" giving directory name. my $eye_dir = __FILE__; chop($eye_dir);chop($eye_dir);chop($eye_dir); sub slurp_yerself { _slurp_tfile($eye_dir . '.pm') } sub get_eye_dir { $eye_dir } 1; __END__ =head1 NAME Acme::EyeDrops - Visual Programming in Perl =head1 SYNOPSIS use Acme::EyeDrops qw(sightly); print sightly( { Shape => 'camel', SourceFile => 'eyesore.pl' } ); =head1 DESCRIPTION C converts a Perl program into an equivalent one, but without all those unsightly letters and numbers. In a Visual Programming breakthrough, EyeDrops allows you to pour the generated program into various shapes, such as UML diagrams, enabling you to instantly understand how the program works just by glancing at its new and improved visual representation. Unlike C and C, the generated program runs without requiring that C be installed on the target system. =head1 EXAMPLES =head2 Getting Started Suppose you have a program, F, consisting of: print "hello world\n"; To convert this little program into an equivalent camel-shaped one, create F as follows: # cvt.pl. Convert helloworld.pl into a camel shape. use Acme::EyeDrops qw(sightly); print sightly( { Shape => 'camel', SourceFile => 'helloworld.pl', Regex => 1 } ); Then run it like this: perl cvt.pl >new.pl After inspecting the newly created program, F, to verify that it does indeed resemble a camel, run it: perl new.pl to confirm it behaves identically to the original F. Instead of using the API, as shown above, you may find it more convenient to use the F command in the F directory: sightly.pl -h (for help) sightly.pl -s camel -f helloworld.pl -r >new.pl cat new.pl (should look like a camel) perl new.pl (should print "hello world" as before) Notice that the shape C<'camel'> is just the file F in the F sub-directory underneath where F is located, so you are free to add your own new shapes as required. For the meaning of Regex => 1 above, see the I section below. =head2 Making Your Programs Easier to Understand If your boss demands a UML diagram describing your program, you can give him this: print sightly( { Shape => 'uml', SourceFile => 'helloworld.pl', Regex => 1 } ); If it is a Windows program, you can indicate that too, by combining shapes: print sightly( { Shape => 'uml,window', Gap => 1, SourceFile => 'helloworld.pl', Regex => 1 } ); producing this improved visual representation: ''=~('('.'?'.'{'.('`'|'%').('['^'-').( ( ( ( ( ( ( ( ( ( ( '`'))))))))))|'!').('`'|',').'"'.('['^ ( ( ( ( ( '+')))) ) ) .('['^')').('`'|')').('`'|'.').(('[')^ ( ( ( ( '/'))))).('{'^'[').'\\'.('"').( '`'|'(').('`'|'%').('`'|"\,").( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( '`'))))))))))))))))))))|"\,").( '`'|'/').('{'^'[').('['^"\,").( '`'|'/').('['^')').('`'|',').('`'|'$').'\\'.'\\' .('`'|'.').'\\'.'"'.';'.('!'^'+').'"'.'}'."\)"); $:='.'^'~';$~='@'|'(';$^=')'^'[';$/='`'|"\.";$,= "\("^ (( '}')) ;($\) =( '`')| "\!"; $: =')'^ "\}"; $~ ='*'| "\`"; $^ ='+'^ "\_"; $/ ='&'| "\@"; $, ='['& "\~"; $\ =','^ "\|"; $: ='.'^ "\~"; $~ ='@'| "\("; $^ =')'^ '[';$/='`'|'.';$,='('^'}';$\='`'|'!';$:=')'^'}'; ($~)= (( '*')) |'`'; $^ ='+'^ "\_"; $/ ='&'| "\@"; $, ='['& "\~"; $\ =','^ "\|"; $: ='.'^ "\~"; $~ ='@'| "\("; $^ =')'^ "\["; $/ ='`'| "\."; $, ='('^ "\}"; $\ ='`'| '!';$:=')'^'}';$~='*'|'`';$^='+'^'_';$/='&'|'@'; $,='['&'~';$\=','^'|';$:='.'^'~';$~='@'|"\(";$^= ')'^'[';$/='`'|'.';$,='('^'}';$\='`'|'!';$:=')'; This is a Visual Programming breakthrough in that you can tell it is a Windows program and see its UML structure too, just by glancing at the code. For Linux only, you can apply its F command to the program's source text: print sightly( { Shape => 'srcbanner', Width => 70, SourceFile => 'helloworld.pl', Regex => 1 } ); The generated program is easier to understand than the original because its characters are bigger and easier to read. =head2 An Abbreviated History of Perl 6 Here is a summary of the Perl 6 development effort so far: print sightly( { Shape => 'jon,larry,damian,simon,parrot,' . 'buffy3,autrijus', Gap => 3, Regex => 1, Print => 1, Indent => 1, SourceString => <<'END_HAIKU' } ); Coffee mug shatters Larry Apocalyptic Parrot not a hoax Design, debate, sift Prankster Piers pawky precis Weekly light relief Gallop Ponie bold! Beer to gulp, Buffy astride Orange sky surrounds Lambda hugs camel Precocious pup productive Sixth pearl glorious END_HAIKU producing: ''=~( '('."\?". '{'.('['^'+' ).('['^"\)").( '`'|')').('`'| '.').('['^'/'). '"'.('`'^'#').( '`'|'/').(('`')| '&').('`'|'&').( '`'|'%').("\`"| '%').('{'^'['). ('`'|('-')).( '['^"\.").( '`'|"'").( '{'^'[').('['^'(') .('`'|'(').('`'|'!') .('['^'/').('['^"\/").( '`'|'%').('['^')').('['^"\(").( '!'^'+').('`'^',').('`'|'!').('['^')').( '['^')').('['^'"').('{'^'[').('`'^'!').('['^'+') .('`'|'/').('`'|'#').('`'|'!').('`'|',').('['^'"'). ('['^'+').('['^'/').('`'|')').("\`"| "\#").( '!'^'+').('{'^'+').('`'|('!')).( '[' ^')').('['^')').('`'|"\/").( '['^'/').('{'^'[').('`'|'.') .('`'|'/').('['^'/').(('{')^ '[').('`'|'!').('{'^'[').('`' |'(').('`'|'/').('`'|('!')).( '['^'#').('!'^'+').('!'^'+').( '`'^'$').('`'|'%').('['^'(').( '`'|')').('`'|"'").('`'|"\."). ','.('{'^'[').('`'|'$').("\`"| '%').('`'|'"').('`'|'!').("\["^ '/').('`'|'%').','.('{'^('[')).( '[' ^'(').('`'|')').('`'|'&').("\["^ '/').('!'^ '+').('{'^'+').('['^')').(('`')| '!').('`'|'.' ).('`'|'+').('['^'(').('['^'/'). +( '`'|'%').('['^')').('{'^'[').('{'^'+').(('`')| ( ( ')'))).('`'|'%').('['^')').('['^'(').('{'^'[').( ( ( '['))^'+').('`'|'!').('['^',').('`'|'+').('['^'"'). (('{')^ '[').('['^'+').('['^')').('`'|'%').('`'|'#').(('`')| ')').('['^ '(').('!'^'+').('{'^',').('`'|'%').('`'|'%').('`'|'+').('`'|',' ).('['^'"').('{'^'[').('`'|',').('`'|')').('`'|"'").('`'|'(').( '['^'/').('{'^'[').('['^')').('`'|'%').('`'|',').('`'|')').('`'| "\%").( "\`"| '&').('!'^'+').('!'^'+').('`'^"'").('`'|'!').( (( '`'))|',').('`'|',').('`'|'/').('['^'+').('{' ^'[').('{'^'+').('`'|'/').('`'|'.').('`'|')' ).('`'|'%').('{'^'[').('`'|('"')).( "\`"| '/').('`'|',').('`'|'$').'!'.('!'^'+' ).('`'^'"').('`'|'%').('`'|'%').("\["^ ')').('{'^'[').('['^'/').('`'|'/').('{' ^'[').('`'|"'").('['^'.').('`'|',').('['^ '+').','.('{'^'[').('`'^'"').('['^"\.").( '`'|'&').('`'|"\&").( '['^'"').('{'^'[').( '`'|'!').('['^"\(").( '['^'/').('['^')'). ('`'|')').('`'|'$').( '`'|'%').('!'^'+') .('`'^'/').('['^ ')').('`'|'!').('`'|'.' ).('`'|"'").('`'|'%').('{'^'[' ).''. ('[' ^'(' ).( '`'| '+' ).+( ( '[' )^(( ( '"' )))) .( ( '{' )^(( ( ( '[' ))) )) .( '[' ^(( (( ( '(' ))) ))). ( '[' ^(( '.') ) ).( '[' ^')' ) .+( '[' ^(')')).( ( '`' )|+ (( ( (( '/' )) ) )) ).( ( ( (( '[' ) )))^+ '.' ) .( '`' | '.').('`'|'$').('['^'(').('!'^('+')).( '!' ^'+').("\`"^ "\,").( ( ( "\`"))| ( '!' ) ) .+( '`' |+ ( ( (( '-' ))) ) ).( ( ( '`'))|'"').( ( ( '`'))|'$').( ( '`' ) | ( ( ( (( '!' ) ) ) )) ) .( ( '{')^ ( ( (( ( (( ( ( ( ( ( ( (( ( '[' ))))))))))))))) ) ).('`'|'('). +( ( ( ( ( ( ( ( ( ( ( ( ( '[' ) ) ) ))) )) ) ) ) )^'.').('`'|"'").('[' ^ ( ( '('))).('{'^'[').(('`')| ( ( ( '#')))).('`'|'!').('`'|'-' ) . ( "\`"| '%').('`'|',') .''. ( ( ( '!') ) ^ '+' ) .( '{' ^'+').('['^')'). ((( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( '`' )) ) ) ))))))))))) ) )))))))))))))|+ '%').('`'|'#').('`'| '/').('`'|'#').('`'|')'). ('`' |'/' ).+( '['^ '.') .''. ('[' ^'(' ).+( '{'^ '[') .''. ('[' ^'+' ).+( '[' ^'.' ).+( '['^ '+').( '{'^ '[') .''. ( '[' ^(( '+' ))).('[' ^ ')' ).( ( '`') |(( ( '/' ))) ) .('`'|'$'). ( '[' ^(( ( ( '.' ))) ) ) .+( '`' | ( '#' )). ( ( '[' )^+ ( ( '/' )) ) . ((( ( (( ( ( (( ( (( ( ( (( ( '`') ))))))))) )))))) )|+ ( ')' )). ((( '[' ))^ '-' ) .( (( ('`')))| "\%").( (( ( ( (( (( '!') )) ) )) ))^+ (( ( ( ( '+' )))))).''. ( '{'^"\(").( ( ( ( (( ( ( ( (( ( (( ( '`' ) )) ) ))) ) ))) )))|')' ) .+( "\["^ ( ( ( '#' )))). ( ( ( '[' ))^'/' ).('`'|'('). ('{' ^'['). ('[' ^'+').( '`'|'%').('`'|'!'). ('[' ^"\)").( '`'|',').('{'^'[').('`'| "'") .(('`')| (( ','))). ('`'|"\/").( '['^')').('`'|')') .('`'| '/').('['^ '.').('['^'(' ).''. ('!'^'+'). ('"'). '}'."\)");$:= '.'^"\~"; $~='@'|'(';$^=')'^'[';$/='`'|'.' ;$,='('^'}';$\='`'|'!';$:=(')')^ '}';$~='*'|'`';$^='+'^('_');$/= '&'|'@';$,='['&'~';$\=','^'|'; $:='.'^'~';$~='@'|'(';$^=')' ^'[';$/='`'|'.';$,=('(')^ '}';$\='`'|'!';$:="\)"^ '}';$~='*'|('`');$^= '+'^'_';$/=('&')| '@';$,='['& '~';$\=','^"\|"; $:='.'^'~'; $~=('@')| '(';$^=')'^'[' ;$/='`'|'.' ;$,='('^"\}";$\= '`'|"\!";$:= ')'^'}';$~='*'|'`' ;$^='+'^"\_"; $/='&'|'@';$,='['&'~' ;$\=','^"\|"; $:='.'^'~';$~='@'|"\("; $^=')'^'[';$/ ='`'|'.';$,='('^"\}";$\= '`'|'!';$:=')' ^'}';$~='*'|'`';$^='+'^'_' ;$/='&'|'@';$, ='['&'~';$\=','^'|';$:='.'^ '~';$~='@'|'('; $^=')'^'[';$/=('`')| '.' ;$, ='('^'}' ;$\='`'|'!';$:=')' ^'}';$~ ='*'|'`';$^=('+')^ '_';$/= '&'|'@';$,='['&'~' ;($\)= ','^'|';$:='.'^'~'; $~='@' |'(';$^=')'^'[';$/= "\`"| '.';$,='('^"\}";$\= ( '`') |'!';$:=')'^'}';$~ = '*' |'`';$^='+'^'_';$/ = (( '&'))|'@';$,="\["& ( (( '~')));$\=','^'|'; ( ( ( ( ( ( ( $:)))))))='.'^"\~"; ( ( ( ( ( $~)))))='@'|'(';$^ =')'^ '[' ; ( ( $/))='`'|'.';$, ='('^'}' ;($\) = ( ( '`'))|'!';$: = ( ')')^"\}"; ( $~) = '*' | ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( '`')))))) ) ) ) ))))))))))))) ) ) )))))))) ; ( ( $^))='+'^'_'; ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( $/))))))) )))))))))))))) ))))))))))='&'|"\@"; $,='['&'~';$\=','^'|';$:= '.'^'~';$~=('@')| '(';$^= ')'^'[';$/='`'|'.';$, ='('^'}' ;$\='`'|'!';$:=')'^'}';$~='*'|'`';$^ ='+'^'_';$/='&'|'@';$,='['&'~';$\="\,"^ '|';$:='.'^'~';$~='@'|'(';$^=')'^('[');$/= '`'|'.';$,='('^'}';$\='`'|'!';$:=')'^'}';$~= '*'|'`';$^='+'^'_';$/='&'|'@';$,='['&"\~";$\= ','^'|';$:='.'^'~';$~='@'|'(';$^=')'^ '[' ;$/='`'|'.';$,='('^'}';$\='`'|'!';$:= ')' ^'}';$~='*'|'`';$^='+'^'_';$/='&'|'@' ;$,='['&'~';$\=','^'|';$:='.'^'~';$~= '@'|'(';$^=')'^'[';$/='`'|'.';$,="\("^ '}';$\='`'|'!';$:=')'^'}';$~='*'|"\`"; $^='+'^'_';$/='&'|'@';$,='['&('~');$\= ','^'|';$:='.'^'~';$~='@'|'(';$^="\)"^ '[';$/='`'|'.';$,='('^'}';$\='`'|'!'; $:=')'^'}';$~='*'|'`';$^='+'^"\_";$/= '&'|'@';$,='['&'~';$\=','^('|');$:= '.'^'~';$~='@'|'(';$^=')'^"\[";$/= '`'|'.';$,='('^'}';$\='`'|"\!";$:= ')'^'}';$~='*'|'`';$^='+'^('_');$/= '&'|'@';$,='['&'~';$\=','^"\|";$:= '.'^'~';$~='@'|'(';$^=')'^('[');$/= '`'|'.';$,='('^'}';$\='`'|"\!";$:= ')'^'}';$~='*'|'`';$^='+'^"\_";$/= '&' |'@';$,='['&'~';$\=','^'|';$:='.'^ ( '~');$~='@'|'(';$^=')'^('[');$/= '`'| '.';$,='('^'}';$\='`'|('!');$:= ')'^'}' ;$~='*'|'`';$^='+'^'_';$/='&'| '@';$,='['& '~';$\=','^'|';$:='.'^'~';$~= '@'|'(';$^=')' ^'[';$/='`'|"\.";$,= (( '('))^'}';$\='`'| '!';$:=')'^('}');$~= ( '*')|'`';$^='+'^'_' ;$/='&'|'@';$,="\["& ( ( '~'));$\=','^'|'; $:='.'^'~';$~='@'|'(' ; $^=')'^'[';$/= '`'|'.';$,='('^'}';$\ ='`'|"\!"; ($:) =')'^'}';$~='*' |'`';$^ =(( '+'))^('_');$/= '&' |+ '@';$,='['&"\~"; ( $\)=','^"\|";$:= '.'^'~';$~="\@"| '(';$^=')'^"\["; $/ ='`'|'.' ;$,=('(')^ '}';$\="\`"| '!' ;$: = ( ( ( ')' )) )^(( '}' ));$~ ='*' |'`'; ( $^)= "\+"^ '_'; $/="\&"| '@' ;($,) = '[' & ( ( ( ( ( ( ( ( ( ( ( ( ( '~')))))))))) ) ) );$\=','^'|';$: = '.'^ '~'; ( $~)='@'|'(';$^= ( ( ')'))^'[';$/= (( ( '`')))|"\.";$,= ( ( '('))^'}'; ( ( $\))='`'|'!'; ( $:)=')'^'}'; $~ = '*'|('`');$^= ( ( ('+')))^ '_'; $/ = '&'|('@');$,= ( '[')&'~' ; ( $\ ) =','^"\|";$:= '.' ^+ '~'; $~= (( ( '@')))|"\(";$^= ')' ^+ (( ( (( '[' ) ))));$/='`'|('.');$,= (( (( '(')) )) ^ '}';$\='`'|'!';$:=')'^ (( '}'));$~ =( '*' )|'`';$^='+'^'_';$/='&'| '@' ;$,='['& (( '~'));$\=','^'|';$:='.'^'~'; $~ ='@' |('(');$^= (')')^ '[';$/='`'|'.';$,='(' ^(( (( '}')))) ;($\)= '`' |'!';$:=')'^"\}";$~= '*'|('`');$^= (( ( '+' )) )^"\_";$/= (( '&') )|'@';$, ='[' &'~' ;$\=',' ^'|';$: =( '.')^"\~"; ($~)= '@'|'(' ;$^ =')' ^(( (( '[' ))) );( $/ )=( (( '`' )) )|'.';$,= (( (( (( '(')) )) ))^'}' ;( ( $\) )='`' |+ '!' ;( $: )= "\)"^ '}' ;( $~ )= (( '*'))|'`'; $^= '+'^ '_' ;( $/ )=(( '&'))| (( '@') );( $, )= '['& '~';$\ ="\,"^ '|' ;( $: )= (( '.'))^'~';$~ =( (( '@' )) )| (( "\("));$^= (( ')' ) )^ (( '[')); $/ ='`' |+ '.' ;$,= ( (( (( (( (( (( '(') ) )) )) ) )) ))^(( ( ( (( '}'))) )) ); $\ ='`' |+ (( '!')) ;( $: )=')'^'}' ;( ($~)) ='*' |+ (( '`'));$^ ='+'^'_';$/= "\&"| '@'; $,= (( '['))&"\~";$\= ','^ '|';$: ="\."^ '~';$~='@'|'(';$^=')'^'[';$/= ('`')| '.';$,='('^"\}";$\= '`'|'!';$:= ')'^'}' ;$~='*'|'`' ;$^='+'^ '_';$/='&' |'@';$,= '['&'~'; $\=','^ '|';$:='.' ^'~';$~= '@'|'(' ;($^) =')'^'[' ;$/='`' |"\."; ($,)= '('^'}' ;($\)= '`'| '!';$: =(')')^ "\}"; ($~)= ('*')| '`';$^='+' ^'_' ;$/=('&')| '@';$,="\["& '~'; $\=','^'|'; $:='.'^"\~"; ($~) ='@'|'(';$^ =')' ^'[' ;($/) ='`'|'.';$, ='(' ^+ '}';$\ ='`'|'!';$:= ')'^'}'; $~='*'| '`';$^='+' ^'_';$/= ('&')| '@';$,= '['&'~' ;$\=',' ^('|');$:= '.'^'~' ;$~='@'|'(';$^ =')'^'['; $/='`'|'.';$,='(' ^'}';$\='`' |'!';$:=')'^'}';$~= '*'|"\`";$^= '+'^'_';$/='&'|"\@"; $,='['&'~';$\ =','^'|';$:='.'^"\~"; $~='@'|'('; $^=')'^'[';$/="\`"| ('.');$,= '('^'}';$\='`'|'!' ;$:="\)"^ '}';$~='*'|'`';$^ ='+'^'_' ;$/='&'|'@';$,="\["& "\~";$\= ','^'|';$:='.'^"\~";$~= '@'|'('; $^=')'^'[';$/='`'|'.';$,= '('^'}'; $\='`'|'!';$:=')'^'}' ;$~='*'|'`' ;($^)= '+'^'_';$/='&'|'@' ;$,='['&"\~"; ($\)= ','^'|';$:="\."^ '~';$~='@'|"\("; $^=')' ^'[';$/='`'|'.' ;$,='('^('}');$\= ('`')| '!';$:=')'^'}' ;$~="\*"| "\`";$^= ('+')^ ( '_');$/=('&')| "\@"; $,='[' &"\~"; ( ( $\)) =',' ^+ "\|"; $:='.' ^(( '~') );($~) ="\@"| '(';$^ =( ')') ^'['; $/='`' |"\."; $,="\("^ (( '}') );$\='`'|('!');$:= (')')^ '}' ;$~= '*'|'`';$^='+'^'_';$/ ="\&"| '@'; ($,) ='['&'~';$\=','^'|';$: ="\."^ '~';$~='@'|'(' ;$^=')'^ "\[";$/= ('`')| '.';$,='('^'}' ;($\) ='`'|'!'; $:=')' ^(( '}'));$~='*'|'`' ;($^) =( '+')^'_';$/=('&')| '@';$, = ( '[')& '~';$\=','^ '|';$: = ( ( ( '.' )))^'~';$~ ="\@"| (( (( '('))));$^ =(')')^ (( ( "\[")));$/= '`'|'.'; $, =( ( (( '('))))^'}' ;$\='`'| '!';$:=')' ^+ '}';$~=('*')| "\`";$^= ( "\+")^ ( '_') ;$/='&'|('@');$,= '['&"\~"; ($\) =','^'|';$:="\."^ '~';$~='@' | ( '('); $^=')'^('[');$/= '`'|'.';$, = ('(')^ '}';$\='`'|'!'; $:=')'^'}'; ( ( ( ( $~))))= '*'|'`';$^='+'^ '_';$/='&' |'@';$, ='['&'~';$\=',' ^"\|";$:= '.'^'~' ;$~='@'|'(';$^= ')'^'['; $/='`'| '.';$,='('^'}'; $\='`'| '!';$:= ')'^'}';$~='*'| '`';$^ ="\+"^ '_';$/='&'|'@'; ($,) ='[' &'~';$\=','^'|' =head2 Just another Perl hacker Let's get more ambitious and create a big self-printing I. my $src = <<'FLAMING_OSTRICHES'; open 0; $/ = undef; $x = <0>; close 0; $x =~ tr/!-~/#/; print $x; FLAMING_OSTRICHES print sightly( { Shape => 'japh', SourceString => $src, Regex => 1 } ); This works. However, if we change: $x =~ tr/!-~/#/; to: $x =~ s/\S/#/g; the generated program malfunctions in strange ways because it is running inside a regular expression and Perl's regex engine is not reentrant. In this case, we must resort to: print sightly( { Shape => 'japh', SourceString => $src, Regex => 0 } ); which runs the generated sightly program via C instead. If you want to use Regex => 1 (to eliminate I alphanumerics), ensure the program to be converted is careful with its use of regular expressions and C<$_>. To produce a I that resembles the original I aka I, try this: print sightly( { Shape => 'merlyn', SourceString => 'Just another Perl hacker,', Regex => 1, Print => 1 } ); producing: ''=~('('.'?'.'{'.('[' ^'+').('['^')').('`'|')').( '`'|'.').('['^'/').'"'.('`'^'*') .('[' ^'.') .('[' ^'(') .('[' ^'/') .('{'^ '[').( "\`"| '!').( '`'| '.').( '`'| ( ( '/'))). ('[' ^ ( ( '/'))).( '`'| ( ( ( ( '('))))). ('`'| ( ( ( ( '%'))))). ('['^ ( ( ( ( ')'))))). ('{'^ '[') .( ( (( ('{'))))^ '+'). ( '`'|'%' ).("\["^ ')').('`' |',').('{'^ '[').('`' |'(').('`' |"\!").( '`'|'#').( ('`')| '+').( '`'|'%') .('['^')') .(( ',' )). '"' .('}'). "\)");$:= ('.')^ ( "\~"); $~='@'| ('(');$^= (( ')' )) ^ (( '[' )) ;($/)= '`'|'.'; $,='('^'}' ; $\='`'|'!' ;($:) =(')')^ ( '}' );($~) = '*' |'`'; ( ( ( $^) ) )= ( ( ( '+' ) ) ) ^ ( ( ( '_' ) ) ) ; ( ( ( $/ ) ) ) = ( ( ( ( ( ( ( ( ( '&')))))))))|'@' ; ( ( ( ( ( ( ( $, ) ) ) ) ) )) = ( (( (( ( ( ( ( ( (( ( ( ( ( '[')) ) ) ) ) ) ) ) ) ) ) ) ) ) ) & ( ( ( ( ( ( ( ( ( ( ( '~' )) ))))))))) =head2 Buffy Looking in the Mirror Because the I encoding is not very compact, you sometimes find yourself playing a surreal form of I, where the winner is the one with the smallest F in: sightly.pl -r -f program_to_be_converted >f.tmp Apart from reducing the (key-)stroke count, you must avoid regexes and strive to replace alphanumeric characters with sightly ones, which do not require sightly encoding. To illustrate, consider the intriguing problem of creating I. Let's start with F: open$[;chop,($==y===c)>$-&&($-=$=)for@:=<0>; print$"x-(y---c-$-).reverse.$/for@: Notice that EyeDrops-generated programs, by default, contain no trailing spaces, which complicates the above program. Buffy looking in the mirror can now be created with: sightly.pl -r -f k.pl -s buffy2 >b.pl cat b.pl (should show Buffy's face) perl b.pl (should show Buffy looking in the mirror) Drat. This requires two I shapes. What to do? Well, you could use the C attribute (C<-T> switch to F) to append the required number of trailing spaces to each line, allowing you to write a briefer F: open$%;chop,print+reverse.$/for<0> and finally produce I with: sightly.pl -Tr -f kk.pl -s buffy2 >bb.pl Alternatively, the C attribute (C<-m> switch to F) could be used to produce a solution free of any trailing spaces: sightly.pl -mr -f k.pl -s buffy2 >buffy.pl cat buffy.pl (should show Buffy's face) perl buffy.pl (should show Buffy looking in the mirror) producing F: ''=~('(?{'.( '`'|'%').('['^'-' ).('`'|'!').('`'|',' ).+ ( '"' ).( ( '`' )|+ ( '/' )). ( '[' ^(( ( '+' ))) ).('`' |(( '%' ))). ( '`' |(( '.') ) ).+ ((( (( ( ((( ((( ( ( ((( ((( ( '\\'))) ))) ) ) ) ) ) )))) )))))) .'$[;' . ( ( ( (( ( ( ( ( ( (( ( ( (( ( ( ( ( ( '`') )))) ) ) ) ) ))) ) ) ))) ) ) )| ( ( ( ( (( '#' ) ) ) ))) ).(('`')| ('(')).( '`'|'/'). ('['^'+') .',(\\$' .'=='.('[' ^'"') . '==='.+( '`'|'#').')' . '>\\$-' .'&&(\\$-=\\' . '$=)'.( '`'|'&').('`' | ( '/')).( '['^')').'\\' . '@:=<' . ('^'^(('`')| "\.")). ( '>').(';').( '!'^'+' ) .('['^'+'). ('['^')' ).('`'| ')').("\`"| "\.").( ( ('['))^ "\/").'\\$\\"'.( ( "\[")^ ( ( ( ( "\#"))))). ( ( '-')) . ( ( ( ('(')))).( ( ( '[')) ^ ( ( '"'))).'--' . '-'. ( ( ( '`'))|'#'). ( ( ( ( '-')))). ( ( ( ( ( '\\' ) ) ) ) ) . ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( '$'))))))))))))))))))))))))).'-).'.('['^ ( ( ( ')')))).('`'|'%').('['^'-').('`'|'%').(('[')^ ( ( ')'))).('['^'(').('`'|'%').'.\\$/'.('`'|'&').( ( ( '`'))|'/').('['^')').'\\@:'.('!'^'+').'"})');$:= ( ( '.'))^'~';$~='@'|'(';$^=')'^'[';$/='`'|'.';$,='(' ; This is perhaps a cleaner solution, though some people find the plain sightly encoding more pleasing to the eye. Showing the face upside down, rather than reflected, is more easily solved with: open$%;print+reverse<0> and easier still for a self-printing shape: open$%;print<0> # self printing open$%;print+map{y;!-~;#;;$_}<0> # replace sightly with '#' =head2 A Somersaulting Camel Let's extend the Buffy example of the previous section to produce a camel-shaped program capable of somersaulting across the screen when run. We start with a generator program, F: print sightly( { Regex => 1, Compact => 1, RemoveNewlines => 1, Indent => 1, BorderGapRight => 1, Shape => 'camel', SourceString => <<'END_SRC_STR' } ); $~=pop||'';open$%; y,!-~,#,,s,(.).,$+,gs,$~&&($_=reverse)for@~=grep$|--,('')x18,<0>; @;=map~~reverse,reverse@~; map{system$^O=~Win?CLS:'clear'; ($-=$_%3)||(--$|,map$_=reverse,@~,@;); print$"x($=/3*abs$|*2-$-),$_,$/for$-&1?@;:@~; sleep!$%}$%..11 END_SRC_STR Note the use of the Compact and RemoveNewlines attributes, necessary here to squeeze the above program into a single camel shape. Running this program: perl gencamel.pl >camel.pl produces F: ''=~('(?{'.( ('`')| '%').('['^'-'). ('`'|'!'). ('`'|',').'"\\$~=' .('['^'+') .('`'| '/').('['^'+').'||'. "'"."'".';'.('`'|'/' ).('['^'+').('`'|'%'). ('`'|'.').('\\$%;').( '['^'"').(',!-~,#,,').( '['^'(').',(.).,\\' .'$+,'.('`'|"'").('['^'(') .',\\$~&&(\\$' .'_='.('['^')').('`'|('%')).( '['^'-').('`'| '%').('['^')').('['^'(').(('`')| '%').')'.("\`"| '&').('`'|'/').('['^"\)").'\\@~='.( '`'|"'").("\["^ ')').('`'|'%').('['^'+').('\\$|--,('). "'"."'".(')').( '['^'#').('^'^('`'|'/')).(':'&'=').',<'. ('^'^('`'|'.') ).'>;\\@;='.('`'|'-').('`'|'!').('['^'+') .'~~'.('['^')' ).('`'|'%').('['^'-').('`'|'%').('['^')'). ('['^'(').('`'|'%').','.('['^')').('`'|'%').('['^'-').('`' |'%').('['^')').('['^'(').('`'|'%').'\\@~;'.('`'|'-').('`'| '!').('['^'+').'\\{'.('['^'(').('['^'"').('['^'(').(('[')^ '/').('`'|'%').('`'|'-').'\\$^'.('`'^'/').'=~'.('{'^"\,").( '`'|')').('`'|'.').'?'.('`'^'#').('`'^',').('{'^'(').(':'). "'".('`'|'#').('`'|',').('`'|'%').('`'|'!').('['^')')."'". ';(\\$-=\\$_%'.('^'^('`'|'-')).')||(--\\$|,'.('`'|'-' ).( '`'|'!').('['^'+').'\\$_='.('['^')').('`'|'%').('[' ^(( '-'))).('`'|'%').('['^')').('['^'(').('`' |('%')). ',' .'\\@~,\\@;);'.('['^'+').('['^(')')).( '`'|')' ).( "\`"| '.').('['^'/').'\\$\\"'.("\["^ ('#')). '(' .'\\$=/'.('^'^('`'|'-')).'*'. (('`')| '!' ).("\`"| '"').('['^ "\("). '\\$|' .+ ('*').( '^'^('`' |',')) .'-\\' .+ '$-),'. '\\$_,'. '\\$' .'/'. ( ('`')| ('&')).( '`'| '/') .('['^ ')').'\\' .'$' .'-' .'&'. (('^')^( '`'| '/') ).'?' .'\\@;' .':' .''. '\\' .'@~;' .''. ('[' ^'(' ).( '`'| ',') .''. ((( '`' ))| '%' ).( '`' |(( '%' ))) .+( '[' ^(( '+' ))) .+ (( '!')). (( (( '\\') )) ). '$%\\}'. (( ((( '\\' ))))) .+ '$' .'%..' .''. ((( '^') )^("\`"| '/' )).( "\^"^( ('`')| ('/'))). '"})'); I. You can run F like this: perl camel.pl normal forward somersaulting camel perl camel.pl b camel somersaults backwards perl camel.pl please do a backward somersault same thing You are free to add a leading C<#!/usr/bin/perl -w> line to F, so long as you also add a blank line after this header line. =head2 Twelve Thousand and Thirty Two Camels In a similar way to the somersaulting camel described above, we create a camel-shaped program capable of emitting twelve thousand and thirty two different camels when run. As usual, we start with a generator program, F: print sightly( { Regex => 1, Compact => 1, RemoveNewlines => 1, BorderGap => 1, Shape => 'camel', SourceString => <<'END_SRC_STR' } ); $~=uc shift;$:=pop||'#';open$%;chop(@~=<0>);$~=~R&& (@~=map{$-=$_+$_;join'',map/.{$-}(.)/,@~}$%..33); $|--&$~=~H&&next,$~!~Q&&eval"y, ,\Q$:\E,c",$~=~I&& eval"y, \Q$:\E,\Q$:\E ,",$~=~M&&($_=reverse), print$~=~V?/(.).?/g:$_,$/for$~=~U?reverse@~:@~ END_SRC_STR Running this program: perl gencamel.pl >camel.pl produces F, which you can run like this: perl camel.pl normal camel perl camel.pl q quine (program prints itself) perl camel.pl m mirror (camel looking in the mirror) perl camel.pl i inverted camel perl camel.pl u upside-down camel perl camel.pl r rotated camel perl camel.pl h horizontally-squashed camel perl camel.pl v vertically-squashed camel And can further combine the above options, each combination producing a different camel, for example: perl camel.pl uri produces a large, bearded camel with a pony-tail, glasses, and a tie-dyed T-shirt. :) F also accepts an optional second argument, specifying the character to fill the camel with (default C<#>). For example: perl camel.pl hv small camel filled with # perl camel.pl hv "$" small camel filled with $ Why 12,032 camels? Combining the main options q, m, i, u, r, h, v can produce 128 different camels. And there are 94 printable characters available for the second argument, making a total of 128 * 94 = 12,032 camels. =head2 Naked Arm Wrestling The final auction at Y::E 2002 in Munich featured an epic athletic contest which you can remember with: use Acme::EyeDrops qw(sightly); my $s = sightly( { Regex => 1, Shape => 'naw', Indent => 1, SourceString => <<'NAKED_ARM_WRESTLING' } ); $/='';open$%;$x=<0>;$y=<0>; substr($y,428,$%)=' AAAAARRRGGGHHH!!!'; map{system$^O=~Win?CLS:'clear'; print$_&1?$y:$x;sleep!$%+($_&1)}$%..9 NAKED_ARM_WRESTLING $s =~ s/ +$//m; print $s; =head2 Baghdad Bob Running this program: print sightly( { Shape => 'baghdad', Regex => 1, Compact => 1, RemoveNewlines => 1, BorderGap => 1, BorderWidthLeft => 3, BorderWidthRight => 3, BorderWidthTop => 2, BorderWidthBottom => 8, SourceString => <<'FAMOUS_COMICAL_ALI_QUOTES' } ); warn+( "Britain is not worth an old shoe!", "There are no American infidels in Baghdad!", "We have them surrounded in their tanks!", "I speak better English than this villain Bush!")[rand(4)],$/ FAMOUS_COMICAL_ALI_QUOTES produces: ''=~('(?{'.('`'|'%').('['^'-').('`'|'!').('`'|',').'"'.('['^',').('`'| '!').('['^')').('`'|'.').'+(\\"'.('`'^'"').('['^')').('`'|')').(('[')^ '/' ).( '`' |'!').('`'|')' ).( '`' |'.').('{'^'[').('`'| ')' ).( '['^'(').('{'^'[').('`'|'.' ).( '`' |'/').('['^'/').('{'^'[').("\["^ ',' ).( '`'|'/').('['^')').('['^'/').("\`"| '(' ).( '{'^'[').('`'|'!').('`'|'.').('{'^'['). ((( '`' ))|'/').('`'|',').('`'|'$').("\{"^ '[' ).( '[' ^'(').('`'|'(').('`'|'/').('`'|'%') .(( '!' )). '\\",\\"'.('{'^'/').('`'|'(').("\`"| '%' ).( '[' ^')').('`'|'%').('{'^'[').('`'|"\!").( '['^ ')' ).( '`'|'%').('{'^'[').('`'|'.').('`'|'/').( "\{"^ '[' ).( '`'^'!').('`'|'-').('`'|'%').('['^"\)").( ('`')| ')' ).( '`'|'#').('`'|'!').('`'|'.').('{'^'[').('`'| ')').('`' |(( '.' ))).('`'|'&').('`'| ')').('`'|'$').('`'| '%' ).( '`'|',').(('[')^ '(').('{'^'['). ((( '`' ))|')').("\`"| '.').(('{')^ '[' ).( '`'^('"')).( '`'|'!'). ((( '`' ))|("'")).( ('`')| '(' ).( '`'|"\$").( "\`"| '!' ).( '`'|"\$"). ('!\\",\\"').( '{'^',').("\`"| '%') .+( '{' ^('[')).( ( '`')|'(' ) .('`' |(( '!' ))).('['^'-' ) . ( ( '`')|'%').('{'^ '[' ).( '['^'/') . ( ( ( '`'))| '(' ).( '`'|'%') . +( ( ( ( '`'))) |(( '-' ))).('{' ^ ( ( ( '['))) ).( '[' ^'(').( '['^'.').('[' ^')').('['^')'). +( (( '`' ))| '/'). ( ( ( '[' ))^ ( ( ( ( ( '.' ))) ) ) ) . ((( '`' ) ) | ( '.' )). ( ( ( ( ( '`' ))) ) |+ '$'). +( ( '`' )|+ ( ( ( '%' ))) ) . ((( '`' ) ) | '$' ).( ( ( ( '{' ))) ^'[').('`' | ( ( ')' ))) . ('`'|'.').('{' ^ '[' ).( ( '[') ^'/' ) . ( '`' |(( ( ( (( ( ( '(' ))) ) )) ))) .+( ( (( '`' ) ))| '%' ).('`'|')').( ( '[' )^+ ( ')')).('{'^ ( '[' )). ( '[' ^(( ( ( '/' ))) ) ).( '`' | '!' ).( ( ( '`' ))| ( ( '.' ))) . ( '`' |(( ( ( '+' ))) )).('['^'(' ).+ '!' .(( '\\')).'",\\"'.('`'^')').('{'^'[').('['^'(').('['^'+').('`'|'%').('`'| '!').('`'|'+').('{'^'[').('`'|'"').('`'|'%').('['^'/').('['^'/').('`'| '%').('['^')').('{'^'[').('`'^'%').('`'|'.').('`'|"'").('`'|',').('`'| ')').('['^'(').('`'|'(').('{'^'[').('['^'/').('`'|'(').('`'|'!').('`'| '.').('{'^'[').('['^'/').('`'|'(').('`'|')').('['^'(').('{'^'[').('['^ '-').('`'|')').('`'|',').('`'|',').('`'|'!').('`'|')').('`'|'.').('{'^ '[').('`'^'"').('['^'.').('['^'(').('`'|'(').'!\\")['.('['^')').("\`"| '!').('`'|'.').('`'|'$').'('.('^'^('`'|'*')).')],\\$/"})');$:='.'^'~'; =head2 99 Bottles of Beer The web site F features programs to display the lyrics of the famous I<99 bottles of beer> song in over 700 different computer languages. Over the years, many different Perl solutions have been proposed. On December 25 1998, for instance, Damian Conway suggested using his Lingua::EN::Inflect module: use Lingua::EN::Inflect 'inflect'; $n=shift||99; print inflect< to C<'s'> and embedding the C inside the first C<@{}> block like so: @{sub b{[@b=(abs||No,bottle.'s'x!!++$_,of,beer),on,the,wall]}b} This saves a stroke because the first C<+b> in the original is replaced by a bald C. Elegant though this solution is, they may have felt a little gobsmacked when the world's leading I golfer, Casey West, uncorked a one stroke solution (C<9>) in that surreal programming language. To produce a solution shaped like a row of beer bottles, run this: use Acme::EyeDrops qw(sightly get_eye_string hjoin_shapes); my $ninety_nine = <<'BURP'; $==pop||99;--$=;sub _{($;=($=||No)." bottle"."s"x!!--$=." of beer")." on the wall"} print+_,", $;! Take one down, pass it around, ",_,"! "while++$= BURP chop($ninety_nine); $ninety_nine =~ s/\nprint/print/; print sightly( { Regex => 1, Compact => 1, ShapeString => hjoin_shapes(2, (get_eye_string('bottle2'))x6), SourceString => $ninety_nine } ); producing: ''=~( '(?{' .('`' |'%') .('[' ^'-') .('`' |'!') .('`' |',') .'"'. '\\$' .'==' .('[' ^'+') .('`' |'/') .('[' ^'+') .'||' .(';' &'=') .(';' &'=') .';-' .'-'. '\\$' .'=;' .('[' ^'(') .('[' ^'.') .('`' |'"') .('!' ^'+') .'_\\{' .'(\\$' .';=('. '\\$=|' ."\|".( '`'^'.' ).(('`')| '/').').' .'\\"'.+( '{'^'['). ('`'|'"') .('`'|'/' ).('['^'/') .('['^'/'). ('`'|',').( '`'|('%')). '\\".\\"'.( '['^('(')). '\\"'.('['^ '#').'!!--' .'\\$=.\\"' .('{'^'['). ('`'|'/').( '`'|"\&").( '{'^"\[").( '`'|"\"").( '`'|"\%").( '`'|"\%").( '['^(')')). '\\").\\"'. ('{'^'[').( '`'|"\/").( '`'|"\.").( '{'^"\[").( '['^"\/").( '`'|"\(").( '`'|"\%").( '{'^"\[").( '['^"\,").( '`'|"\!").( '`'|"\,").( '`'|(',')). '\\"\\}'.+( '['^"\+").( '['^"\)").( '`'|"\)").( '`'|"\.").( '['^('/')). '+_,\\",'.( '{'^('[')). ('\\$;!').( '!'^"\+").( '{'^"\/").( '`'|"\!").( '`'|"\+").( '`'|"\%").( '{'^"\[").( '`'|"\/").( '`'|"\.").( '`'|"\%").( '{'^"\[").( '`'|"\$").( '`'|"\/").( '['^"\,").( '`'|('.')). ','.(('{')^ '[').("\["^ '+').("\`"| '!').("\["^ '(').("\["^ '(').("\{"^ '[').("\`"| ')').("\["^ '/').("\{"^ '[').("\`"| '!').("\["^ ')').("\`"| '/').("\["^ '.').("\`"| '.').("\`"| '$')."\,".( '!'^('+')). '\\",_,\\"' .'!'.("\!"^ '+').("\!"^ '+').'\\"'. ('['^',').( '`'|"\(").( '`'|"\)").( '`'|"\,").( '`'|('%')). '++\\$="})' );$:=('.')^ '~';$~='@'| '(';$^=')'^ '[';$/='`'; A larger single beer bottle shape can be produced with: print sightly( { Regex => 1, Compact => 1, Shape => 'bottle', SourceString => $ninety_nine } ); while the canonical solution, shaped like 99 bottles of beer, can be generated with: print sightly( { Regex => 1, ShapeString => join("\n", (hjoin_shapes(3, (get_eye_string('bottle2'))x3))x33), SourceString => $ninety_nine } ); =head2 Sierpinski Triangles A simple and concise Sierpinski triangle generator, F, is: #!perl -l $x=2**pop;print$"x--$x,map$x&$_?$"x2:"/\\",0..$y++while$x which was posted by Mtv Europe to golf@perl.org on 14-sep-2002 as a one stroke improvement on Adam Antonik's original program. Running: perl siertri.pl 4 displays a Sierpinski triangle with 2**4 lines. Proclaiming Mtv's program as the shortest (in Acme::EyeDrops 1.13) only served to provoke Adam Antonik and Eugene van der Pijll into shortening it by exploiting a hard C<$^F>, as shown in some of the examples below: -l print$"x--$x,map$x&$_?$"x2:"/\\",0..$_-1for 1..($x=2**pop) -l $x=2**pop;print$"x--$x,map$x&$_?$"x2:"/\\",0..$y++while$x -l $^F**=pop;print$"x--$^F,map$^F&$_?$"x2:"/\\",0..$y++while$^F -lX061 print$"x--$/,map$/&$_?$"x2:"/\\",0..$y++while$/<<=pop -l print$"x--$^F,map$^F&$_?$"x2:"/\\",0..$y++while$^F*=2**pop -l $_=$"x2**pop;$_="$'/\\",print,s/(?<=\\)../$&^KI^D5/egwhile/^ / An interesting obfuscated Sierpinski triangle generator is: #!/usr/bin/perl -l s--@{[(gE^Ge)=~/[^g^e]/g]}[g^e]x((!!+~~g^e^g^e)< 1, Compact => 1, RemoveNewlines => 1, Indent => 1, BorderGap => 1, BorderWidth => 2, # For 'siertri' built-in shape, Width=>5 means: # height is 2**5 lines # width is 2 * 2**5 characters Width => 5, Shape => 'siertri', SourceString => <<'END_SRC_STR' } ); $-=!$%<<(pop||4);print$"x$-,map($-&$_?' ':'/\\',$%..$.++),$/while$--- END_SRC_STR producing: ''=~('(?{'.('`'|'%').('['^'-').('`'|'!').('`'|"\,").'"\\$-=!\\$%<<('.( '['^'+').('`'|'/').('['^'+').'||'.('^'^('`'|'*')).');'.('['^'+').('['^ (( (( (( (( (( (( ')') )) )) )) )) )) )) .(('`')| (( (( (( (( (( (( ')') )))) )) )) )) )) .( (( (( (( '`'))))))|'.').( (( (( (( (( (( (( '[') )))) )) )) )) )^ (( (( (( (( '/'))))) )))).''. (( (( (( (( (( (( (( (( '\\' )))) )))) )))) )) )) .+ (( (( (( (( (( (( (( (( (( '$')))))))))))))))))).'\\"'.('[' ^+ (( (( (( (( (( '#') )))) )) )) )) .+ (( (( (( (( '\\')))) )))).'$' .+ (( (( (( (( (( (( (( '-') )))) )))) )))) ). (( (( (( (( (( (( (( (( (( (( (( ','))))))))))))) ))))))))).("\`"| (( (( (( (( (( (( (( (( '-') )))) )))) )))) )) )) .( (( (( (( (( (( (( (( (( (( '`'))))) )))))))) )))))|(( '!'))).( (( (( (( (( (( (( (( (( (( (( (( (( '[') )))) )))) )))) )))) )))) )))^ '+') .+ (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( '(')))))))))))))))))))))))))))))))))))))).'\\$-&\\$_?'."'".('{'^ (( (( (( '['))))))).('{'^'[')."'".':'."'".'/\\\\\\\\'."'".',\\$%..\\$.++),\\$/' .('['^',').('`'|'(').('`'|')').('`'|',').('`'|'%').'\\$---"})');$:='.' =head2 Dueling Dingos During the TPR02 Perl Golf tournament, I<`/anick> composed a poem describing his experience, entitled I. You can produce a program that emits his moving poem like this: print sightly( { Shape => 'yanick3', Regex => 1, Print => 1, SourceString => <<'END_DINGO' } ); #!/usr/bin/perl # Dueling Dingos v1.1, by Yanick Champoux (9/4/2002) # # Inspired by the TPR(0,2) Perl Golf contest. # Name haven't been changed, since the involved # parties could hardly be labelled as 'innocent', # and are way far too gone to protect anyway. wait until localtime > @April[0]; # wait until the first of April BEGIN{} study and seek FOR, $some, $inspiration; write $stuff; $score = 145; # no good; delete $stuff { I_can_do_without } and do $more_stuff; delete $even{more_stuff}; reverse $engineer; study; eval $strategy and redo; write, write, write; delete $_{'!'}, delete $"{"@!"}, delete $@{'*'}; # must stop cursing use less 'characters', $durnit; read THE, $current, $solution; not 2, $bad; delete $white_spaces{''} until $program == glob; for( $all, my @troubles ) { unlink 1, $character; } ARGH: $must, not $despair; $I->can(do{ $it }); study new Idea; m/mmmm/m... do{able}; kill $chickens; 'ask', $Nanabozo, 2, bless $me, 'with more inspiration'; $so, close; warn $mailing_list and alarm $Andrew; $toil until my $solution < /-\ndrew's /; GOT_IT: send $solution, $to, ref; $brain, shutdown I,'m dead'; goto sleep; wait; $till, $the, $day, $after; readline last $scoreboard; grep $all, stat; read THE, $stats, $again until $it_sinks_in; $Andrew,'s score' lt $mine; $eyeball, pop @o ; END_DINGO The generated program, being 2577 lines long, is not reproduced here. To generate a shorter program summarising I<`/anick>'s TPR02 anguish: print sightly( { Shape => 'yanick,eye,mosquito,coffee', Gap => 3, Regex => 1, Print => 1, SourceString => <<'END_SUFFERING' } ); My head is hurting, my right eye feels like it's going to pop like a mosquito drinking from an expresso addict with high blood pressure, I want to crawl somewhere damp and dark and quiet and I consider never to touch a keyboard again. END_SUFFERING producing: ''=~('('.'?'.'{'.( '['^'+').('['^')').('`'| ')').('`'|'.').('['^'/').'"' .('`'^'-').('['^'"').('{'^'[').( '`'|'(').('`'|'%').('`'|'!').("\`"| '$').('{'^'[').('`'|')').('['^('(')).( '{'^'[').('`'|'(').('['^'.').('['^"\)").( '['^'/').('`'|')').('`'|'.').('`'|"'").','. ('{'^'[').('`'|'-').('['^'"').('{'^'[').('['^ ')').('`'|')').('`'|"'").('`'|'(').('['^'/').( '{'^'[').('`'|'%').('['^'"').('`'|'%').('{'^'[' ).('`'|'&').('`'|'%').('`'|'%').('`'|',').(('[')^ '(').('{'^'[').('`'|',').('`'|')').('`'|'+').("\`"| '%').('{'^'[').('`'|')'). ('['^ ('/')). "'".('['^'(').('{'^'['). ("\`"| "'").('`'|'/').('`'|')' ).''. ('`'|'.').('`'|"'").('{' ^'[') .('[' ^'/').('`'|'/').(('{')^ '['). ('['^'+' ).('`'|'/').('['^'+'). ('!'^ '+').('`'| ','). ('`'|')').('`' |'+') .('`'|'%') .''. ('{'^"\[").( "\`"| '!').('{'^ ( "\[")).( "\`"| '-').('`'| ( '/')).( "\["^ '(').('['^ ( '*' )). ('['^'.' ) .+( ( ( ( ( ( ( '`')))) ) )| ( ( ( ')'))) ) . ( ( "\[")^ ( ( '/'))) . ("\`"| '/'). ( ('{')^ '[' ).('`' |'$') . ("\["^ ( ( ( ')'))) ) . ( ('`')| ( ')')) . ( ('`')| ( '.')) . ( ('`')| ( ( ( '+')))) . +( ( ( ('`')))| ( (( ( ( ')')))))). ( (( ( ( '`'))))|'.'). ( (( ( ( '`'))))|"'").('{'^('[')).( (( ( '`')))|'&').('['^')').("\`"| (( ( '/')))).('`'|'-').('{'^'[').( '`'| '!').('`'|'.').('{'^'[').("\`"| ( '%')).('['^'#').('['^'+').("\["^ ( ')')).('`'|'%').('['^'(').('[' ^'(').( '`'|'/').('{'^'[').('`'|'!'). ('`'|'$').('`'|'$').('`'|')' ).('`'|'#').('[' ^ '/').('{'^'[').(('[')^ ( ( ','))).('`'|')').('['^'/') . ( '`'|'(').('{'^'[').('`'|'(').( ( ( '`'))|')').('`'|"'").('`'|'(').( ( ( '!'))^'+').('`'|'"').('`'|(',')).( ( ( '`'))|'/').('`'|'/').('`'|('$')).( ( ( '{'))^'[').('['^'+').('['^')').('`'| ( ( '%'))).('['^'(').('['^'(').('['^'.') . ( '['^')').('`'|'%').','.('{'^('[')).( ( ( '`'))^')').('{'^'[').('['^',').('`'| ( ( '!'))).('`'|'.').('['^'/').('{'^'[') . ( '['^'/').('`'|'/').('{'^'[').('`'| ( ( '#'))).('['^')').('`'|'!').(('[')^ ( ( ','))).('`'|',').('{'^'[').('['^ ( ( '('))).('`'|'/').('`'|('-')).( ( ( '`'))|'%').('['^',').('`'| ( ( '('))).('`'|'%').('['^ ( ')')).('`'|'%'). +( (( '{')) ^ ( "\[")).( ( ( '`'))|'$' ) . ( ( '`')|'!').( ( ( ( ( '`'))))|'-') . ( ( ( '['))^'+').( ( ( ( ( '{'))))^'[') . ( ( ( '`'))|'!').( ( ( ( ( '`'))))|'.' ) . ( ( '`')|'$' ).('{'^'[' ) . ( ( '`')|'$').('`'| ('!')).( '['^ ')' ) . ('`'|'+').('{'^'[') .('`'|'!').('`'| ( '.')).('`'|'$').('!'^'+').('['^"\*").( ( '[')^'.').('`'|')').('`'|'%').('['^'/' ).('{'^'[').('`'|'!') .('`'|'.'). ('`'|('$')).( '{'^('[')).( '`'^')' ).( '{' ^+ ( ( ( (( ( ( ( (( ( ( ( (( ( ( ( (( ( ( ( '[') ) ) ) )) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) . ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( '`')))) )))))) ))))) )|'#' ).''. ('`'| '/').('`'|'.').('['^'(').('`'|')').(('`')| '$').( '`'|'%').('['^')').('{'^'[').('`'|('.')).( '`'|'%'). ('['^'-').('`'|'%').('['^')').('{'^'[').('['^ '/' ).('`'|'/').('{'^'[').('['^'/').('`'|'/').( '[' ^'.').('`'|'#').('`'|'(').('{'^'[').("\`"| '!' ).('{'^'[').('`'|'+').('`'|'%').('['^'"' ).( '`'|'"').('`'|'/').('`'|'!').('['^')').( '`'| '$').('{'^'[').('`'|'!').('`'|("'")).( '`'| '!').('`'|')').('`'|'.').'.'.(('!')^ '+') .'"'.'}'.')');$:='.'^'~';$~='@'|'(';$^= ')'^'[';$/='`'|'.';$,='('^'}';$\='`' |'!';$:=')'^'}';$~='*'|'`';$^= '+'^'_';$/='&'|('@');$,= '['&'~';$\=','^'|' =head2 Error Handling The C function returns a properly shaped program string; there is no error return. If something is badly wrong, C is called. So if you are calling C in an environment where it's unacceptable to die, be sure to wrap the C call in an C block. For example: eval { $prog = sightly( { Shape => 'invalid-shape', SourceFile => 'eyesore.pl', InformHandler => sub {} } ); }; if ($@) { warn "sightly died: $@\n" } =head2 EyeDropping EyeDrops.pm To illustrate EyeDropping a non-trivial module, we convert F itself into a I, via the following generator program, F: use Acme::EyeDrops qw(sightly); # Slurp EyeDrops.pm into $orig string. my $orig = Acme::EyeDrops::slurp_yerself(); # Split $orig into the source ($src) and the pod ($doc). my ($src, $doc) = split(/\n1;\n/, $orig, 2); # Remove the line containing $eye_dir = __FILE__ ... # because this line confuses eval. $src =~ s/^(my \$eye_dir\b.*)$//m; # Generate the new sightly version of EyeDrops.pm. print $1, sightly( { Regex => 0, Compact => 1, TrapEvalDie => 1, FillerVar => ';#', Shape => 'camel', Gap => 1, SourceString => $src } ), ";\n1;\n", $doc; Running this program: perl mkeye.pl >m.tmp produces F, which can be copied over the top of the original F in the Acme directory. Though the new I version passes the regression test suite, it's about 4 times slower than the original. Notice that we used Regex => 0 (since EyeDrops.pm uses many regular expressions), TrapEvalDie => 1 (since EyeDrops.pm calls the C function) and FillerVar => ';#' (to avoid possible warnings due to unused variables). Notice too that the only known I line in EyeDrops.pm: my $eye_dir = __FILE__; ... was extracted and inserted at the top of the new file. =head2 Encoding Binary Files But wait, there's more. You can encode binary files too. print sightly( { Shape => 'camel,mongers', SourceFile => 'some_binary_file', Binary => 1, Print => 1, Gap => 3 } ); This is prettier than I. To encode: sightly.pl -g3 -bps camel,mongers -f some_binary_file >eyesore To decode: perl eyesore >f.tmp To verify it worked: cmp f.tmp some_binary_file =head2 A Slow Day On a really slow day, you can sit at your Unix terminal and type things like: sightly.pl -r -s camel -f helloworld.pl >t1.pl cat t1.pl perl t1.pl Just one camel needed for this little program. sightly.pl -r -s camel -f t1.pl >t2.pl cat t2.pl perl t2.pl Hmm. 14 camels now. sightly.pl -r -s camel -f t2.pl >t3.pl ls -l t3.pl cat t3.pl perl t3.pl 195 camels. 563,745 bytes. Hmm. Getting slower. Is this the biggest, slowest I program ever written? sightly.pl -r -s camel -f t3.pl >t4.pl ls -l t4.pl cat t4.pl perl t4.pl 2046 camels. 5,172,288 bytes. Out of memory! =head2 Buffy Goes to the Cricket Buffy fans might like to rotate her letters: print sightly( { Shape => 'buffy', Rotate => 0, # try 270, 90 and 180 RotateType => 1, # try 0, 1, 2 SourceFile => 'helloworld.pl', Regex => 1 } ); or have her ride a pony: print sightly( { Shape => 'buffy3,buffy4,riding,a,pony', SourceString => "This is how Catherine the ". "Great died.\n", Gap => 2, Regex => 1, Print => 1 } ); while cricket fans might create a reduced, inverted shape with: print sightly( { Shape => 'cricket', Reduce => 1, Invert => 1, BorderWidth => 1, SourceFile => 'helloworld.pl', Regex => 1 } ); producing: ''=~('('.'?'.'{'.('`'|('%')).( '['^"\-").( '`'|'!').('`'|',' ).'"'.('[' ^'+').('['^')').( '`'|')'). ('`'|'.').('['^'/' ).("\{"^ '[').'\\'.'"'.('`'| "\(").( (( '`'))|'%').('`'| ',') .( '`'|',').("\`"| '/' ).( '{'^'[').("\["^ (( ',') )).('`'|"\/").( ( (( '[')))^')').( (( ( '`')))|',').( '`' | '$').'\\'.''. '\\' .('`'|"\."). '\\'. (( '"'))."\;".( '!'^"\+"). '"'.'}'.')') ;$:=('.')^ '~';$~="\@"| '(';$^=')' ^'[';$/='`'| ('.');$,= '('^'}';$\= '`'|'!'; $:=')'^'}' ;$~='*' |"\`";$^= '+' ^+ ( '_');$/= '&'| '@'; $,='['& '~'; $\=',' ^"\|"; ($:)= ('.')^ "\~"; $~='@'| "\("; ($^) =')'^'[';$/ =('`')| '.' ;$,='('^'}' ;$\=('`')| ( '!');$:=')' ^'}';$~='*'| (( '`'));$^='+'^'_';$/='&'|'@';#; =head2 Snowflakes The C and C attributes (C<-t>/C<-u> switches to F) are handy when you simply want to pour some I text into a shape. To illustrate, consider an entry in the I 2002 Christmas programming contest, F: $_=q~vZvZ&%('$&"'"&(&"&$&"'"&$Z$#$$$#$%$&"'"&(&# %$&"'"&#Z#$$$#%#%$%$%$%(%%%#%$%$%#Z"%*#$%$%$%$%(%%%#%$%$ %#Z"%,($%$%$%(%%%#%$%$%#Z"%*%"%$%$%$%(%%%#%$%$%#Z#%%"#%#% $%$%$%$##&#%$%$%$%#Z$&""$%"&$%$%$%#%"%"&%%$%$%#Z%&%&# %"'"'"'###%*'"'"'"ZT%?ZT%?ZS'>Zv~; s;\s;;g; $;='@,=map{$.=$";join"",map((($.^=O)x(-33+ord)),/./g),$/}split+Z; s/./(rand)<.2?"o":$"/egfor@;=((5x84).$/)x30;map{ system$^O=~W?CLS:"clear";print@;;splice@;,-$_,2,pop@,; @;=($/,@;);sleep!$%}2..17'; $;=~s;\s;;g;eval$; The rules of this contest state that the program source code must fit precisely into the provided snowflake shape. To comply, you can pour the above program into the required shape with: sightly.pl -s snow -f snowing.pl -t -u# -n1 >snowflake.pl or equivalently (using the API instead of F): print sightly( { Shape => 'snow', SourceFile => 'snowing.pl', Text => 1, TextFiller => '#', Indent => 1 } ); producing a valid entry, F: $_= q~v ZvZ&%(' $&"'"&( &"& $&"' "&$Z$#$$$#$%$& "'"& (&# %$&"'"&#Z#$$ $#%# %$%$%$%(%%%# %$%$%#Z"%*#$ %$%$ %$%(%%%#%$%$ %# Z"%, ($% $% $%( %%%# %$ %$% #Z" %*%" %$ %$%$ %(% %%# %$%$%# Z#%%"#%#%$ %$ %$%$##&#%$ %$%$%# Z$ &""$%"&$%$%$%#%"%"&%%$%$%#Z%&% &# %"'"'"'###%*'"'"'"ZT%?ZT%?ZS'>Zv~;s;\s;;g;$;='@,=map {$.= $";join"" ,map((($ .^=O)x(- 33+ord)), /./g ),$ /}split +Z;s/. /(rand )<.2?"o ":$ "/eg for@;=((5 x84).$/) x30;map{ system$^O =~W? CLS:"clear";print@;;splice@;,-$_,2,pop@,;@;=($/,@;); sl eep!$%}2..17';$;=~s;\s;;g;eval $; ###### ########## ## ########## ###### ### ### #### ## #### ### ### ## #### ### ## ### #### ## ############ #### ############ ############ #### ############ ### #### ############## #### ### ####### ####### ### ### Running F produces a pretty C snow-scape. The leftover space at the bottom could be used to add a snowman: $_= q~v ZvZ&%(' $&"'"&( &"& $&"' "&$Z$#$$$#$%$& "'"& (&# %$&"'"&#Z#$$ $#%# %$%$%$%(%%%# %$%$%#Z"%*#$ %$%$ %$%(%%%#%$%$ %# Z"%, ($% $% $%( %%%# %$ %$% #Z" %*%" %$ %$%$ %(% %%# %$%$%# Z#%%"#%#%$ %$ %$%$##&#%$ %$%$%# Z$ &""$%"&$%$%$%#%"%"&%%$%$%#Z%&% &# %"'"'"'###%*'"'"'"ZT%?ZT%?ZS'>Zv~;s;\s;;g;$~=q~ZZZJ_ #_ZH /'\\ZG|#o #o#|ZG|$ <%|ZH\\" \\!_!_!/" /ZG/ )\\ ZF/+\\Z E|-|ZE |-|ZE| -|ZF\\+ /ZG \\)/ ~;;@x=@,= +map{$.= $";;join "",map((( $.^= O)x(-33+ord)),/./g)}split+Z;$~=~s~\s~~g;;s;.;(rand)< .2 ?"o":$";egxfor@;=(5x84)x30;map {# system $^O=~W?CLS :+ "clear";;; ;print $_. $/, ,for $_ -18? @;: ### (( map{ $|= 1; ;;; join "" ,map($|--?$" x(-3 *11+ord):$_, /./g)}split+ Z,$~ ),@x);splice @;, -$_, 2,pop@,;@;=("" ,@;) ;;; ;sleep! $%}+2.. 18# /-\ =head1 REFERENCE =head2 Sightly Encoding There are 32 characters in the sightly character set: ! " # $ % & ' ( ) * + , - . / (33-47) : ; < = > ? @ (58-64) [ \ ] ^ _ ` (91-96) { | } ~ (123-126) A I consists only of characters drawn from this set. The C function converts an ASCII string (0-255) to a sightly string; the C function does the reverse. =head2 Function Reference =over 4 =item ascii_to_sightly STRING Given an ascii string STRING, returns a sightly string. =item sightly_to_ascii STRING Given a sightly string STRING, returns an ascii string. =item regex_print_sightly STRING Given an ascii string STRING, returns a sightly-encoded Perl program with a print statement embedded in a regular expression. When run, the program will print STRING. =item regex_eval_sightly STRING Given a Perl program in ascii string STRING, returns an equivalent sightly-encoded Perl program using an eval statement embedded in a regular expression. =item clean_print_sightly STRING Given an ascii string STRING, returns a sightly-encoded Perl program with a print statement executed via eval. When run, the program will print STRING. =item clean_eval_sightly STRING Given a Perl program in ascii string STRING, returns an equivalent sightly-encoded Perl program using an eval statement executed via eval. =item regex_binmode_print_sightly STRING Given an ascii string STRING, returns a sightly-encoded Perl program with a binmode(STDOUT) and a print statement embedded in a regular expression. When run, the program will print STRING. Note that STRING may contain any character in the range 0-255. This function is used to sightly-encode binary files. This function is dodgy because regexs don't seem to like binary zeros; use C instead. =item clean_binmode_print_sightly STRING Given an ascii string STRING, returns a sightly-encoded Perl program with a binmode(STDOUT) and a print statement executed via eval. When run, the program will print STRING. Note that STRING may contain any character in the range 0-255. This function is used to sightly-encode binary files. =item get_builtin_shapes Returns a list of the built-in shape names. =item get_eye_dir Returns the directory containing the F<.eye> file shapes. This is the F sub-directory underneath where F is located. =item get_eye_shapes Returns a list of the I shapes in ascii-betical order. An eye shape is just a file with a F<.eye> extension residing in the F directory. =item get_eye_keywords Returns a hash reference keyed by keyword, with the value being the list of shapes containing the keyword. =item find_eye_shapes KEYWORDLIST Returns a list of the I shapes in ascii-betical order that contain all keywords in KEYWORDLIST. The keywords in KEYWORDLIST are implicitly AND'ed together. Additionally, you may use OR inside any KEYWORDLIST element. If this is unclear, see the examples in "Shape Properties" section below. =item get_eye_string SHAPENAME Given a .eye SHAPENAME, returns the shape string. =item get_eye_properties SHAPENAME Given a .eye SHAPENAME, returns a hash reference of the shape properties or undef if the shape has no properties. =item slurp_yerself Returns a string containing the contents of F. =item make_triangle WIDTH Returns a triangle shaped string of WIDTH characters. =item make_siertri WIDTH Returns a Sierpinski triangle shaped string containing 2**WIDTH lines. =item make_banner WIDTH STRING Linux only. Returns a banner of STRING, using the Linux command C. =item border_shape SHAPESTRING GAP_LEFT GAP_RIGHT GAP_TOP GAP_BOTTOM WIDTH_LEFT WIDTH_RIGHT WIDTH_TOP WIDTH_BOTTOM Put a border around a shape. =item invert_shape SHAPESTRING Invert a shape. =item reflect_shape SHAPESTRING Reflect a shape. =item reduce_shape SHAPESTRING FACT Reduce the size of a shape by a factor of FACT. =item expand_shape SHAPESTRING FACT Expand the size of a shape by a factor of FACT. =item rotate_shape SHAPESTRING DEGREES RTYPE FLIP Rotate a shape clockwise thru 90, 180 or 270 degrees. RTYPE=0 big rotated shape, RTYPE=1 small rotated shape, RTYPE=2 squashed rotated shape. FLIP=1 to flip (reflect) shape in addition to rotating it. RTYPE and FLIP do not apply to 180 degrees. =item hjoin_shapes GAP SHAPESTRINGLIST Join the shapes specified by SHAPESTRINGLIST horizontally with GAP spaces between each shape. =item pour_text SHAPESTRING TEXTSTRING GAP FILLTEXT Given a shape string SHAPESTRING, a string TEXTSTRING, and a GAP between successive shapes, returns a properly shaped string. That is, pour TEXTSTRING into SHAPESTRING. FILLTEXT (typically '#') is text to be used as a filler for any leftover part of the shape (if not set, don't fill in leftovers). =item pour_sightly SHAPESTRING PROGSTRING GAP RFILLVAR COMPACT IH Given a shape string SHAPESTRING, a sightly-encoded program string PROGSTRING, and a GAP between successive shapes, returns a properly shaped program string. That is, pour PROGSTRING into SHAPESTRING. RFILLVAR is either a reference to an array of filler variables or, alternatively, a string to fill the leftover of the last shape with. Common filler strings are C<''> for no filler at all, or C<'#'> or C<';'> or C<';#'>. A filler variable is a valid Perl variable consisting of two characters: C<$> and a punctuation character. For example, RFILLVAR = C<[ '$:', '$^', '$~' ]>. Do not use C<$;> or C<$"> or C<$_> as filler variables. If COMPACT is 1, use compact sightly encoding, if 0 use plain sightly encoding. If IH (inform handler) is undef, prints status of what it is doing to STDERR; you can override this by providing a subroutine reference taking a single inform string argument. To shut it up, set IH to C. =item sightly HASHREF Given a hash reference, HASHREF, describing various attributes, returns a properly shaped program string. There is no error return; if something is badly wrong, C is called -- so wrap the call to C in an eval block if you can't afford to die. The attributes that HASHREF may contain are: Shape Describes the shape you want. First, a built-in shape is looked for. Next, a 'eye' shape (.eye file in the get_eye_dir() directory unless overridden by the EyeDir attribute) is looked for. Finally, a file name is looked for. ShapeString Describes the shape you want. This time you specify a shape string. SourceFile The source file name to convert. SourceHandle Specify a file handle instead of a file name. SourceString Specify a string instead of a file name. BannerString String to use with built-in Shape 'banner'. Regex Boolean. If set, try to embed source program in a regular expression. Do not set this flag when converting complex programs. Compact Boolean. If set, use compact sightly encoding. Print Boolean. If set, use a print statement instead of the default eval statement. Set this flag when converting text files (not programs). Binary Boolean. Set if encoding a binary file. Text Boolean. Set if pouring unsightly text. TextFiller Filler string used with Text attribute. For example, TextFiller => '#'. Gap The number of lines between successive shapes. Rotate Rotate the shape clockwise 90, 180 or 270 degrees. RotateType 0 = big rotated shape, 1 = small rotated shape, 2 = squashed rotated shape. RotateFlip Boolean. Set if want to flip (reflect) the shape in addition to rotating it. Reflect Boolean. Reflect the shape. Reduce Reduce the size of the shape. Expand Expand the size of the shape. Invert Boolean. Invert the shape. Indent Indent the shape. The number of spaces to indent. TrailingSpaces Boolean. Ensure all lines of the shape are of equal length, adding trailing spaces if required. RemoveNewlines Boolean. Remove all newlines from the source before conversion. BorderGap Put a border around the shape. Gap between border and the shape. BorderGapLeft,BorderGapRight,BorderGapTop,BorderGapBottom You can override BorderGap with one or more from the above. BorderWidth Put a border around the shape. Width of border. BorderWidthLeft,BorderWidthRight,BorderWidthTop,BorderWidthBottom You can override BorderWidth with one or more from the above. Width Ignored for .eye file shapes. For built-in shapes, interpreted appropriately for the shape, typically the shape width in characters. If no shape is specified, a rectangular block of Width characters is generated. EyeDir Normally .eye files are got from the EyeDrops directory underneath where EyeDrops.pm is located. You can override that by specifying a directory containing the .eye shape files. InformHandler By default, sightly prints status of what it is doing to STDERR; you can override this by providing a subroutine reference taking a single inform string argument. To shut it up, set to sub {}. TrapEvalDie Boolean. Add closing 'die $@ if $@' to generated program. When an eval code block calls the die function, the program does not die; instead the die string is returned to eval in $@. Using this flag allows you to convert programs that call die. TrapWarn Boolean. Add leading 'local $SIG{__WARN__}=sub{};' to generated program. This shuts up some warnings. Use this option if generated program emits 'No such signal: SIGHUP at ...' when run with warnings enabled. FillerVar Reference to a list of 'filler variables'. A filler variable is a Perl variable consisting of two characters: $ and a punctuation character. For example, FillerVar => [ '$:', '$^' ]. Do not use $; or $" or $_ as filler variables. Alternatively, you may set this to '' if you don't want any filler, or to a string (e.g. '#' or ';' or ';#') to use instead of filler variables to fill the leftover part of the last shape with. =back =head2 Specifying a Shape When you specify a shape like this: sightly( { Shape => 'fred' ... first a built-in C shape is looked for, then EyeDrops looks for the file F in the F directory. If you specify a C<'/'> or C<'.'> in the Shape attribute, a file with that name is looked for instead, for example: sightly( { Shape => '/tmp/fred.eye' ... Finally, you may specify a shape with a string, for example: my $shapestr = <<'FLAMING_OSTRICHES'; ##### ####################### FLAMING_OSTRICHES sightly ( { ShapeString => $shapestr ... If you specify a shape without a source file: print sightly( { Shape => 'camel' } ); a I filler is used to fill the shape. If you specify a source file without a shape: print sightly( { SourceFile => 'helloworld.pl' } ); a shapeless sightly string without any spaces or newlines is generated. You can break this string into fixed width lines via the Width attribute: print sightly( { SourceFile => 'helloworld.pl', Width => 40 } ); Generally, you should specify the Width attribute of I shapes. Notice that the Width attribute is ignored for F<.eye> file shapes. =head2 Shape Reference The I shapes are: banner Linux banner command (/usr/games/banner -w Width) of text in BannerString attribute srcbanner Linux banner command (/usr/games/banner -w Width) of source text siertri A Sierpinski triangle (2**Width lines) triangle A triangle (width Width characters) all A shape consisting of all .eye shapes joined together (Width blank lines between each shape) The F<.eye> file shapes distributed with this version of EyeDrops are: a Horizontal banner of "a" acme Perl/Parrot/Ponie Euro-hacker and modern artist who likes the colour orange and enjoys having his bra-strap twanged adrianh Perl qa expert alien An alien (rumoured to be Ton Hospel, from the Roswell archives circa 1974) alpaca Lama pacos, from South America, with long shaggy hair and related to the llama autrijus The father of Pugs baghdad Baghdad Bob aka Comical Ali beer Beer glass designed by Matthew Byng-Maddick for the cam.pm Beerfestival Perl Programming Contest 2002 bighorn Ovis canadensis (bighorn sheep) found in the Rocky Mountains bleach Vertical banner of "use Acme::Bleach;" bottle A bottle of beer bottle2 Abbreviated version of shape bottle bottle3 A bottle of champagne with a champagne glass buffy Vertical banner of "Buffy" buffy2 Buffy's angelic face buffy3 Buffy riding a pony buffy4 Horizontal banner of "Buffy" camel Dromedary (Camelus dromedarius, one hump) camel2 Another dromedary (from use.perl.org) camel3 London.pm's bactrian camel at London zoo campm Horizontal banner of "cam.pm" candle A Christmas candle china1 Chinese characters, roughly translated as "God is added a year of seniority; human is added a year of age, Spring fills the universe; luck and happiness fills the family" coffee A cup of coffee cricket Australia are world champions in this game damian The Acme namespace is all his fault dan The father of parrot debian Debian logo (contributed by Richard Hartmann) dipsy Teletubbies Dipsy (also london.pm infobot name) eugene Champion Perl golfer, Drs Eugene van der Pijll of Utrecht, Holland eye An eye flag_canada Canada's flag, contributed by `/anick gelly Featured speaker at every session of Y::E 2003, Paris golfer A golfer hitting a one iron halloween A witch riding a broomstick heart A heart shape contributed by `/anick hipowls A pair of hip owls japh JAPHs were invented by Randal L Schwartz in 1988 jon Kick-started the Perl 6 development effort by smashing a standard-issue white coffee mug against a hotel wall jon_oxer Linux Australia bigwig (contributed by Paul Fenwick) kangaroo A kangaroo kansai_pm Kansai.pm's mascot (Tiger with Perl characters) contributed by Takanori KAWAI (Japanese) kermit Kermit the frog koaladile A cross between a koala and a crocodile larry Wall, Larry (as opposed to Russell Wall who is Wall, Russ) larry2 Caricature of Larry contributed by Ryan King llama Llamas are so closely related to camels they can breed with them (their progeny are called camas) london Haiku "A Day in The Life of a London Perl Monger" map_australia Map of Australia map_italy Map of Italy map_japan Map of Japan map_uk Map of United Kingdom and Ireland map_world1 World globe, Asian view map_world2 World globe, African view map_world3 World globe, American view merlyn Just another Perl hacker, aka Randal L Schwartz mongers Perl Mongers logo moose A moose moosecamel A moose and a camel (modelled after http://irclog.perlgeek.de) mosquito A mosquito music A musical symbol naw Naked Arm Wrestling (Y::E 2002, Munich) opera Opera browser logo (contributed by Cosimo) panda A panda designed by Yanni Ellen Liu parrot Originally an April fool's joke, the joke was that it was not a joke pgolf Perl Golf logo (inspired by `/anick) pony Horizontal banner of "Pony" pony2 Picture of a Pony pugs Horizontal banner of "Pugs" pugs2 Picture of a Pugs dog riding Horizontal banner of "riding" rose A rose santa Santa Claus playing golf santa2 Santa Claus carrying presents saturn The planet Saturn schwern is my bitch schwern2 Shape schwern without the banner simon The inventor of parrot smiley A smiley face smiley2 Pulling a face smiley3 A sad face snow Snowflake designed by Matthew Byng-Maddick for the cam.pm Christmas Perl Programming Contest 2002 spoon A wooden spoon tonick Pictorial representation of a golf contest between Ton Hospel and `/anick; colourful but not very suspenseful tpr Vertical banner of "The Perl Review" uml A UML diagram undies A pair of underpants window A window writing_perl Perl in camel-style by Takanori KAWAI (Japanese) yanick Caricature of `/anick's noggin yanick2 Uttered by `/anick during TPR02 yanick3 Pictorial version of yanick2 yanick4 Abbreviated version of shape yanick It is easy to create your own shapes. For some ideas on shapes, point your search engine at I or I. If you generate some nice shapes, please send them in so they can be included in future versions of EyeDrops. =head2 Shape Properties All the F<.eye> shape files have a corresponding F<.eyp> shape property file, specifying the shape's properties. Currently, the allowed shape properties are: name nick description cpanid author authorcpanid source keywords where valid keywords are: face person perlhacker animal object planet map flag sport underwear hbanner vbanner logo debian opera To give an example of how shape properties might be used, to find all shapes that depict just the faces of perl hackers: use Acme::EyeDrops qw(find_eye_shapes); my @perlhackers = find_eye_shapes('face', 'person', 'perlhacker'); Note that there is an implicit AND between each keyword; that is, the above code finds all shapes with face AND person AND perlhacker keywords. Additionally, you may use OR in any argument, for example: my @perlhackers = find_eye_shapes('face', 'person OR animal', 'perlhacker'); finds all shapes matching face AND (person OR animal) AND perlhacker. Instead of using the API, as shown above, you may also use the F command in the F directory: findshapes.pl -h (for help) findshapes.pl -v face person perlhacker The last example displays the faces and properties of all perl hackers. Please note that these shape properties are experimental and may change in future A::E releases. =head1 BUGS A really diabolical shape with lots of single character lines will defeat the shape-pouring algorithm. You can eliminate all alphanumerics (via Regex => 1) only if the program to be converted is careful with its use of regular expressions and C<$_>. To convert complex programs, you must use Regex => 0, which emits a leading unsightly double C. The code generated by Regex => 1 requires Perl 5.005 or higher in order to run; when run on earlier versions, you will likely see the error message: C. The converted program runs inside an C which may cause problems for non-trivial programs. A C statement or an C block, for instance, may cause trouble. If desperate, give the C and C attributes a go, and see if they fix the problem. If the program to be converted uses the Perl format variables C<$:>, C<$~> or C<$^> you may need to explicitly set the C attribute to a Perl variable/s not used by the program. Linux F does not support the following characters: \ [ ] { } < > ^ _ | ~ When the CPAN Text::Banner module is enhanced, it will be used in place of the Linux banner command. =head1 AUTHOR Andrew Savige =head1 SEE ALSO Acme::EyeDrops lightning talk by Flavio Poletti at YAPC::Europe 2008 at F. Software Art page at F. Acme's Y::E 2002 naked arm wrestling movie at F. Japanese translations of selected CPAN modules (including Acme::EyeDrops) can be found at F. (Japanized Perl Resources Project is at F). Perl Obfuscation Engines, for example, yaoe by Perl Monk mtve, at F. More information on 99 bottles of beer can be found at F and F. Similar sites exist for I programs F, and I F. To learn more about HQ9+ programming visit F and F. Perl Monks Obfuscation section. In particular, ideas for EyeDrops shapes were got from: F (Erudil's camel code), F (Spaghetti Obfu), F (Just another Bach Hacker), F (hello world), F (Sierpinski Triangle), F (Halloween JAPH ?), F (Saturn). The Y::E 2002 I talk by Thomas Klausner at F. Les Perl Mongueurs de Paris $A++ page at F. Yanni Ellen Liu's excellent Ascii Art collection formerly at F. More information on I can be found at F. More information on koaladiles (and their relationship to kwalitee) can be found at F. The cam.pm Obfuscated Programming Contests at F. I was played monthly at F and is now played mostly at F. Ton Hospel's excellent generic golf tester can be got from F. The C<--$|> idiom (exploited in the I section) is "explained" in this thread: F. L L L L L =head1 CREDITS I blame Japhy and Ronald J Kimball and others on the fwp mailing list for exposing the ''=~ trick, Jas Nagra for explaining his C module, and Rajah Ankur and Supremely Unorthodox Eric for provoking me. I would also like to thank Ian Phillipps, Philip Newton, Ryan King, Michael G Schwern, Robert G Werner, Simon Cozens, and others on the fwp mailing list for their advice on ASCII Art, imaging programs, and on which picture of Larry to use. Thanks also to Mtv Europe, Ronald J Kimball and Eugene van der Pijll for their help in golfing the program in the I section. Keith Calvert Ivey also contributed some levity to this section. Ideas from Adam Antonik, Mtv Europe, Eugene van der Pijll, Ton Hospel and Keith Calvert Ivey were used in the I section. Thanks cog for the prod to add new Shape Properties feature. The jon shape was derived from F. Kudos to Elaine -HFB- Ashton for showing me this. The merlyn shape was derived from this photo F of Randal singing a duet with Samantha Fox. The simon shape was derived from a pencil sketch by the Japanese artist Eiko Yamashita. The candle, china1, panda and santa2 shapes were derived from similar ones at Yanni Ellen Liu's Ascii Art collection. =head1 COPYRIGHT Copyright (c) 2001-2008 Andrew Savige. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.