Shell-style glob pattern matching for Standard ML.
sml-glob compiles a glob pattern once and matches it against whole strings
(matching is anchored, as with filename globbing). It is a pure backtracking
matcher with no regex dependency.
| Pattern | Matches |
|---|---|
* |
any run of characters, including empty |
? |
exactly one character |
[abc] |
one of the listed characters |
[a-z] |
one character in the range |
[!...] / [^...] |
one character not listed (negation) |
\c |
the literal character c (escape) |
Pure Standard ML using only the Basis library -- no FFI, no threads. Verified on MLton and Poly/ML.
make test # build + run the suite under MLton (default)
make test-poly # run the suite under Poly/ML
make all-tests # run under both
make cleansmlpkg add github.com/sjqtentacles/sml-glob
smlpkg syncThen reference the library basis from your own .mlb:
lib/github.com/sjqtentacles/sml-glob/glob.mlb
For Poly/ML, use the glob.sig and glob.sml sources in order.
val ok = Glob.matchString "*.sml" "foo.sml" (* true *)
val no = Glob.matchString "*.sml" "foo.txt" (* false *)
val c = Glob.matchString "src/*.[ch]" "src/main.c" (* true *)
(* compile once, match many *)
val p = Glob.compile "data_???"
val a = Glob.matches p "data_001" (* true *)
val b = Glob.matches p "data_1" (* false *)
(* case-insensitive *)
val ci = Glob.caseInsensitive "Hello*World"
val d = Glob.matches ci "HELLO, WORLD" (* true *)Escapes make special characters literal:
Glob.matchString "a\\*b" "a*b" (* true: the * is literal *)
Glob.matchString "a\\*b" "axxb" (* false: not a wildcard *)val p = Glob.compile "*.sml"
val srcs = Glob.filter p ["a.sml","b.txt","c.sml"] (* ["a.sml","c.sml"] *)
val (yes, no) = Glob.partition p ["a.sml","b.txt"] (* (["a.sml"], ["b.txt"]) *)expand produces every literal alternative (the cartesian product across
groups, with nesting); compileBrace compiles each one to a pattern.
Glob.expand "{a,b}c" (* ["ac","bc"] *)
Glob.expand "{a,b}{x,y}" (* ["ax","ay","bx","by"] *)
Glob.expand "foo.{c,h}" (* ["foo.c","foo.h"] *)
val ps = Glob.compileBrace "{a,b}.sml" (* two patterns *)matchPath treats / as a separator: a single */? will not cross it, and
** (globstar) matches across separators, including zero segments.
Glob.matchPath "src/*.sml" "src/x.sml" (* true *)
Glob.matchPath "src/*.sml" "src/sub/x.sml" (* false: * stops at / *)
Glob.matchPath "src/**/*.sml" "src/a/b/x.sml" (* true *)
Glob.matchPath "src/**/x.sml" "src/x.sml" (* true: ** matches zero dirs *)Glob.compileOpt "a[bc" (* NONE: unterminated class *)
Glob.validate "a[bc" (* SOME "unterminated '[' character class" *)
Glob.isLiteral (Glob.compile "abc") (* true *)
Glob.literalPrefix (Glob.compile "src/*.sml") (* "src/" — prune a dir walk *)
Glob.toRegexString (Glob.compile "a*b?.sml") (* "^a.*b.\\.sml$" *)| Function | Description |
|---|---|
compile : string -> pattern |
Compile a glob pattern (lenient). |
compileOpt : string -> pattern option |
Strict compile; NONE on malformed. |
validate : string -> string option |
SOME msg on malformed, else NONE. |
matches : pattern -> string -> bool |
Match a compiled pattern (anchored). |
matchString : string -> string -> bool |
Compile + match in one step. |
matchPath : string -> string -> bool |
Path-aware match (/-respecting, **). |
caseInsensitive : string -> pattern |
Compile a case-insensitive pattern. |
filter : pattern -> string list -> string list |
Keep matching strings. |
partition : pattern -> string list -> string list * string list |
Split by match. |
expand : string -> string list |
Brace expansion to literal alternatives. |
compileBrace : string -> pattern list |
Compile each brace expansion. |
literalPrefix : pattern -> string |
Leading literal run before first wildcard. |
isLiteral : pattern -> bool |
True if the pattern has no wildcards. |
toRegexString : pattern -> string |
Anchored regex equivalent. |
MIT. See LICENSE.