#!/usr/local/bin/perl use Parse::RecDescent; use strict; my %lslr; my $pwd; my $parser; sub cmd_pwd { print $pwd !~ m!^/! ? "/" : "", "$pwd\n"; } sub cmd_cd { my $newdir = shift; for my $dir (keys %lslr) { if (".$pwd/$newdir" eq $dir) { $pwd .= "/$newdir"; return; } } print "No such file or directory\n"; } sub cmd_ls { my $dir = shift || $pwd; print $_, "\n" for @{$lslr{".$dir"}}; } sub cmd_quit { exit } sub find_targets { grep { m!^\.(/)?$pwd! } keys %lslr; } sub cmd_dfs { my $exp = shift; do_find($exp, sort +find_targets()); } sub cmd_bfs { my $exp = shift; do_find( $exp, map {$_->[0]} sort { $a->[0] cmp $b->[0] && $a->[1] <=> $b->[1] } map { [$_, scalar split m!/!] } find_targets() ); } sub do_find { my $match = $parser->expression(@_); for my $basedir (@_) { print map { "$basedir/" . (/\s(\S+)$/)[0] . "\n" } grep { /^(.)\S+\s+\d+\s+(?:\S+\s+){2}(\d+).*\s(\S+)$/; $match->( { type => $1 eq 'd' ? 'd' : 'f' , size => $2 , name => $3 } ); } @{$lslr{$basedir}} } print "\n"; } my %cmdtbl = ( pwd => \&cmd_pwd , cd => \&cmd_cd , ls => \&cmd_ls , dfs => \&cmd_dfs , bfs => \&cmd_bfs , quit => \&cmd_quit ); sub getcmd ($) { my $name = shift; $cmdtbl{$name} || sub { print "$name: command not found\n"; } } sub init_sh { my $fname = shift; local $/ = ""; open my $fh, $fname or die $!; %lslr = map {sub {substr(shift,0,-1), \@_}->(split /\n/)} <$fh>; } my $grammer = <<'FINDEXP'; { use List::Util qw(reduce); } expressions: expression '-o' expression { my ($lexp, $rexp) = @item[1,3]; sub { $lexp->(@_) || $rexp->(@_) } } | expression(s? /(-a)?/) { my @exps = grep {!/-a/} @{$item[1]}; sub { reduce {$a->(@_) && $b->(@_) } @exps } } expression: '(' expressions ')' { my $exps = $item{expressions}; sub { $exps->(@_) } } | '!' expression { my $exp = $item{expression}; sub { !$exp->(@_) } } | '-name' /[a-zA-Z0-9\._-]+/ { my $name = $item[2]; sub { shift->{name} eq $name } } | '-type' /d|f/ { my $type = $item[2]; sub { shift->{type} eq $type } } | '-size' /[+-]?[1-9][0-9]+/ { my $size = $item[2]; sub { shift->{size} == $size } } FINDEXP sub init_parser { $parser = Parse::RecDescent->new($grammer); die unless defined $parser; } sub main_loop { while (1) { print "lslR> "; my ($cmd, @args) = grep {$_} split /\s+/, ; next unless $cmd; getcmd ($cmd) -> (join ' ', @args); } } init_sh(shift); init_parser(); main_loop();