use v6;
grammar Glob {
token TOP { <term>+ }
token term {
|| <match>
|| <char>
}
token match { <match-any> | <match-one> }
token match-any { '*' }
token match-one { '?' }
token char { . }
}
class GlobMatcher {
has @.terms;
method ACCEPTS(Str:D $s) {
my @backtracks = [ \([ $s.comb ], [ @!terms ]) ];
BACKTRACK: while @backtracks.pop -> (@letters, @terms) {
LETTER: while @letters.shift -> $c {
last LETTER unless @terms;
my $this-term = @terms.shift;
my $next-term = @terms[0];
given $this-term {
when '*' {
# Continue to the next term if we can
if @terms and $next-term ne '*' | '?' and $c eq $next-term {
push @backtracks, ([ @letters ], [ '*', |@terms ]);
redo;
}
# Match anything, and try again next round
unshift @terms, $this-term;
}
# We have a letter, so we match!
when '?' { }
# Only match exactly
default {
# If not an exact match, we fail; try again if we can
next BACKTRACK if $c ne $this-term;
}
}
}
# If we matched everything, we succeed
return True unless @terms;
# Otherwise, try the next backtrack, if any
}
# We ran out of back tracks, so we fail
False;
}
}
class GlobAction {
method TOP($/) { make GlobMatcher.new(terms => $<term>.map(~*)) }
method term($/) { make $/.made }
method match($/) { make $/.made }
method match-any($/) { make '*' }
method match-on($/) { make '?' }
method char($/) { make ~$/ }
}
my $matcher = Glob.parse("*.txt", :actions(GlobAction.new)).made;
for <foo.txt bar.txt baz.html blah.blah.txt> -> $f {
say "$f matches" if $f ~~ $matcher;
}
sub glob($glob, :$globlang = Glob) {
$globlang.parse("*.txt", :actions(GlobAction.new)).made;
}
for <foo.txt bar.txt baz.html blah.blah.txt>.grep(glob('*.txt')) -> $f {
next unless $f ~~ glob('*.txt');
say "$f matches";
}
<foo.txt bar.txt baz.html blah.blah.txt>.grep(glob('*.txt')).say;
grammar SQLGlob is Glob {
token match-any { '%' }
token match-one { '_' }
}
<foo.txt bar.txt baz.html blah.blah.txt>.grep(glob('%.txt', :globlang(SQLGlob))).say;