#!/usr/bin/perl # # by Erik Osheim # # licensed under the GNU GPL version 2 # try not to pollute the namespace use Data::Dumper qw(Dumper); use File::Basename qw(); use Getopt::Long qw(); use Perl6::Slurp qw(slurp); use Scalar::Util qw(); use Term::ReadLine qw(); Getopt::Long::Configure('bundling'); $| = 1; $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 1; sub usage { my($status) = @_; my $prog = File::Basename::basename($0); print < sub { usage(0); }, 'eval|e=s' => sub { push(@preload, ['eval', $_[1]]); }, 'pipe|p' => sub { $pipe = 'line' }, 'quiet|q' => sub { $verbose = 0 }, 'run|r=s' => sub { push(@preload, ['run', $_[1]]); }, 'use|u=s' => sub { push(@preload, ['use', $_[1]]); }, 'verbose|v' => sub { $verbose = 'please'; }, ) || usage(1); my ($prompt, $input) = (">>>", ""); unless($pipe) { print "Welcome to Erik's Perl Interpreter\n"; print "(Type \"help\", \"exit\", or something else)\n"; } my $pkgregex = qr/^(?:[a-zA-Z_][a-zA-Z0-9_]*::)+$/; my $nameregex = qr/^[a-zA-Z_][a-zA-Z0-9_]*$/; sub sigilfind { my ($pkg, $sigil, $base, $proc) = @_; $base = "" unless $base; $proc = {$pkg => 1} unless $proc; $pkg = $base if $base; my @results; foreach my $item (eval("keys(\%${pkg})")) { my $item2 = $base . $item; next if $proc->{$item2}; if($item =~ m/$nameregex/) { push(@results, $item2) if eval("defined(${sigil}::${base}${item})"); } elsif($item =~ m/$pkgregex/) { $proc->{$item2} = 1; push(@results, sigilfind($item2, $sigil, $item2, $proc)); } } return @results; } sub _repr { my ($item) = @_; if(!defined($item)) { return 'undef'; } elsif(Scalar::Util::blessed($item)) { return "$item"; } elsif(ref($item) eq 'CODE') { return "$item"; } else { my $s = Dumper($item); $s =~ s/\n+$//; return $s; } } sub repr { my @output; foreach my $item (@_) { push(@output, _repr($item)); } return join("\n", @output); } sub escape { my($s) = @_; $s =~ s/\\/\\\\/g; $s =~ s/\n/\\n/g; return $s; } sub draw_prompt { my($p) = @_; print $pipe ? "$prefix:" . escape($p) . "\n" : $p; } sub draw_completions { my (@items) = @_; if($pipe) { print 'COMPLETIONS:' . join('|', @items) . "\n"; } else { print join("\n", @items) . "\n"; } } sub complete { my ($word) = @_; $word =~ m/^([&\$%@\*]?)(.*)$/; my $sigil = $1 ? $1 : '&'; my $name = $2; my @candidates = grep { $_ =~ m/^$name/ } sigilfind('main::', $sigil); return map { "${1}$_" } sort(@candidates); } my $HELP = <[0] eq 'eval') { eval $pair->[1]; die "failed to eval '$pair->[1]': $@" if $@; } elsif($pair->[0] eq 'run') { die "no path named $pair->[1] found" unless -e $pair->[1]; my $data = slurp($pair->[1]); eval $data; die "failed to run $pair->[1]: $@" if $@; } elsif($pair->[0] eq 'use') { eval "use $pair->[1]"; die "failed to use $pair->[1]: $@" if $@; } } unless($pipe) { $term = Term::ReadLine->new('IPERL'); my $attribs = $term->Attribs(); $attribs->{special_prefixes} = '$@%*&'; $attribs->{completion_function} = sub { my ($word, $line, $x) = @_; return complete($word); }; } while(1) { my $line; if($pipe) { draw_prompt($prompt); $line = ; } else { $line = $term->readline("$prompt "); } last unless defined($line); chomp($line); if($pipe) { if($line =~ m/ENTER:(.*)$/) { $line = $1; } else { if($line =~ m/COMPLETE:(.*)$/) { draw_completions(complete($1)); } else { print "malformed pipe input line\n"; } next; } } if($line eq '') { } elsif($line eq 'exit') { last; } elsif($line eq 'help') { ($input, $prompt) = ("", ">>>"); print $HELP; } elsif($line eq 'reload') { ($input, $prompt) = ("", ">>>"); print "reloading...\n"; exec($0, @OLDARGV); } elsif($line eq 'sh') { ($input, $prompt) = ("", ">>>"); system("bash") unless $pipe; } elsif($line =~ m/^sh (.+)$/) { ($input, $prompt) = ("", ">>>"); system($1) unless $pipe; } elsif($line =~ m/^:tab *(.*)$/) { } elsif($line eq ':code') { draw_completions(sigilfind('main::', '&')); } elsif($line eq ':scalar') { draw_completions(sigilfind('main::', '$')); } elsif($line eq ':hash') { draw_completions(sigilfind('main::', '%')); } elsif($line eq ':array') { draw_completions(sigilfind('main::', '@')); } elsif($line eq ':glob') { draw_completions(sigilfind('main::', '*')); } elsif($line =~ m/^[\t ]/ || $line =~ m/\{ *$/) { $input .= $line; $prompt = "..>"; } elsif($line =~ m/\\ *$/) { $line =~ s/\\ *$//; $input .= $line; $prompt = "..>"; } else { $input .= $line; my @results = map { $_ =~ s/\n$//; repr($_); } eval($input); if($@) { print $@; } elsif(scalar(@results) < 2) { print $results[0] . "\n"; } else { print join(", ", @results) . "\n"; } ($input, $prompt) = ("", ">>>"); } } print "Bye.\n"; } main();