Skip to content

regex: add --regextype=SHGLOB (GLOB + {a,b} brace alternation) (#129) - #334

Merged
helly25 merged 1 commit into
mainfrom
feat/shglob-brace-regextype
Jul 11, 2026
Merged

regex: add --regextype=SHGLOB (GLOB + {a,b} brace alternation) (#129)#334
helly25 merged 1 commit into
mainfrom
feat/shglob-brace-regextype

Conversation

@helly25

@helly25 helly25 commented Jul 11, 2026

Copy link
Copy Markdown
Owner

What

A new match grammar --regextype=SHGLOB ("shell glob"): everything GLOB does, plus shell brace alternation, so *.{cc,h} matches either extension. As a matcher (not a shell) a brace group compiles to an RE2 alternation - *.{cc,h} -> [^/]*\.(?:cc|h) - it matches any one alternative rather than expanding to several words.

Why a separate grammar (not a GLOB feature)

GLOB and the gitignore engine must keep matching literal {/} (a .gitignore line foo{1} matches literally), so brace expansion has to be opt-in. Named SHGLOB, deliberately not EXTGLOB - in bash that names the pattern-lists ?(..)/@(..)/!(..), which we do not ship (the !(..) negation has no clean RE2 form).

Semantics (match bash)

pattern RE2 note
*.{cc,h} [^/]*\.(?:cc|h) the motivating case
{src,test}/**/x (?:src|test)/(?:.*/)?x alts contain / and **
{a,{b,c}d} (?:a|(?:b|c)d) nesting is recursive
{a,,b} (?:a||b) empty alternatives allowed
{a} \{a\} no top-level comma -> literal
\{a,b\} \{a,b\} \{ \} \, escape to literals

Deferred: numeric/char sequences {1..9}/{a..z}, and the extglob pattern-lists.

Implementation (DRY)

xff::glob gains an internal flavor-aware TranslateInto shared by GlobToRegex (braces off) and the new ShglobToRegex (braces on, recursive {...} parse); the **/[...] logic (incl. the #128 class scanner) is not duplicated. Wired through Grammar::kShglob (regex Compile), parser GrammarFromGlobals, run.cc ValidateRegextype (SHGLOB is a core engine, always linked), and the globals/doc-renderer SOT.

Self-documentation (+ the PCRE2 note)

--regextype gains a full details body documenting every grammar. It also fixes a gap: the help always listed PCRE2 but never noted it may be absent. Now the details state PCRE2 is the one build-time extra, present only in a full build, and point to xff --help=extras for whether THIS binary has it (RE2/EXACT/FNMATCH/GLOB/SHGLOB are always built in). Flows into --help=--regextype / --help=full / --man / --markdown.

Also resolves #122

glob.h documents why xff keeps its own gitignore-** translator rather than migrating onto mbo::file::Glob2Re2 (different **; xff walks its own VFS engine, needs only the pure pattern->RE2 step).

Test

  • glob_test: ShglobToRegex alternation/nesting/degenerate cases + a GLOB-keeps-braces-literal guard.
  • regex_test: kShglob compile + match + retained GLOB path semantics.
  • grep_test.sh: --regextype=SHGLOB end-to-end ({TODO,FIXME}) + GLOB literal-brace.
  • Full //xff/... green.

Closes #129. Resolves #122.

SHGLOB is a new match grammar: everything GLOB does, plus shell brace alternation, so
`*.{cc,h}` matches either extension. As a matcher (not a shell) a brace group becomes an
RE2 alternation - `*.{cc,h}` -> `[^/]*\.(?:cc|h)` - rather than expanding to several words.

Why a separate grammar and not a GLOB feature: GLOB and the gitignore engine must keep
matching literal `{`/`}` (a .gitignore line `foo{1}` matches literally), so brace
expansion has to be opt-in. Named SHGLOB ("shell glob"), deliberately NOT EXTGLOB - in bash
that names the pattern-lists ?(..)/@(..)/!(..), which we do not ship (the !(..) negation has
no clean RE2 form).

Semantics match bash: each alternative is itself SHGLOB-translated (nesting, `*`/`?`/`[...]`
inside; alts may contain `/` and `**`); a comma-less `{x}` or an unbalanced `{` stays a
literal brace; empty alternatives are allowed; `\{`/`\}`/`\,` escape to literals, as do
`{`/`}`/`,` inside a `[...]` class. Deferred: numeric/char sequences {1..9}/{a..z} and
extglob pattern-lists.

Implementation is DRY: xff::glob gains an internal flavor-aware TranslateInto shared by
GlobToRegex (braces off) and the new ShglobToRegex (braces on, recursive {...} parse); the
`**`/`[...]` logic (incl. the #128 class scanner) is not duplicated. Wired through
Grammar::kShglob (regex Compile), parser GrammarFromGlobals, run.cc ValidateRegextype
(SHGLOB is a core engine, always linked), and the globals/doc-renderer SOT.

Self-doc: --regextype gains a full details body documenting every grammar. It also gives
PCRE2 the note it was missing - PCRE2 is the one build-time extra, present only in a full
build, and `xff --help=extras` reports whether THIS binary has it (RE2/EXACT/FNMATCH/GLOB/
SHGLOB are always built in). Flows into --help=--regextype / --help=full / --man / --markdown.

Also resolves #122: glob.h documents why xff keeps its own gitignore-`**` translator rather
than migrating onto mbo::file::Glob2Re2 (different `**`; xff walks its own VFS engine).

Tests: glob_test (ShglobToRegex + GLOB-braces-literal cases), regex_test (kShglob compile +
match + path semantics), grep_test.sh (--regextype=SHGLOB end-to-end + GLOB literal-brace).
Full suite green.
@helly25
helly25 enabled auto-merge (squash) July 11, 2026 10:58
@helly25
helly25 merged commit 3f57cf7 into main Jul 11, 2026
9 checks passed
@helly25
helly25 deleted the feat/shglob-brace-regextype branch July 11, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant