RegexEditor.dev Logo

Regex Editor & Tester

Build, test, and debug regular expressions

New FlavorCS EducationAutomata Theory

Formal Regular ExpressionsThe Language Your CS Professor Uses

A new flavor for the theoretical regular expressions from formal language theory and automata — built for CS students, educators, and anyone who wants to test textbook exercises directly in the browser.

3
Core operators
5
Special symbols
44
Worked examples
∞
Test strings

Why this flavor exists

Every undergraduate CS curriculum covers formal languages and automata — the mathematical foundations of computation. In these courses, students encounter regular expressions that look familiar but behave very differently from the regex they know from programming.

The problem is concrete: when a student writes a+b in a textbook exercise, they mean "a or b" (alternation). If they paste that into any standard regex tester, it matches "one or more a's followed by b" — completely different semantics. There was no way to test formal RE exercises in a real engine.

The Core Problem

A student types a+b into a regex tester to verify their homework. The tester says it matches aab. The student thinks they made an error. They didn't — the tool was wrong for this context.

The Formal flavor bridges this gap. It translates formal notation to JavaScript regex on the fly, enforces full-string matching, and provides a sandboxed environment that speaks the same language as lectures and textbooks.

What is a formal regular expression?

In formal language theory, a regular expression over an alphabet Σ is defined inductively by exactly three rules:

Base cases:
∅ L(∅) = {}
ε L(ε) = {""}
a ∈ Σ L(a) = {"a"}
Inductive cases (r, s are regular expressions):
r·s L(rs) = { xy | x∈L(r), y∈L(s) }
r | s (also r+s) L(r|s) = L(r) ∪ L(s)
r* L(r*) = L(r)⁰ ∪ L(r)¹ ∪ L(r)² ∪ …

The profound result — proved by Stephen Kleene in 1956 — is that these three operations are sufficient to describe exactly the class of regular languages, which coincides with the languages accepted by finite automata (DFAs and NFAs). This is Kleene's theorem.

This flavor adds one practical extension: Σ as a shorthand for "any single alphabet character" — analogous to . in programming regex, but restricted to exclude newlines (which separate test strings).

Syntax reference

SymbolNameMeaningJS equivalent
∅Empty setNo string accepted, not even ε(?!)
εEmpty stringAccepts only the empty string(?:)
aLiteralOne specific alphabet charactera
ΣAny charOne char from alphabet (not newline)[^\n]
rs or r·sConcat.r followed immediately by srs
r|s or r+sUnionEither r or s (+ ≠ one-or-more!)r|s
r*Kleene starZero or more copies of rr*
(r)GroupingOverride precedence, no capture(?:r)

Operator Precedence (high → low)

*Kleene star — binds most tightly
·Concatenation
|Alternation — binds most loosely

So ab*|c parses as (a(b*))|c, not a(b*|c).

⚠ Critical Difference

In formal RE, + is alternation / union, not "one or more". a+b ≡ a|b = {a, b}. To express "one or more a's", write aa*.

Formal RE vs. Programming regex

Formal RE (CS theory)

  • —3 operators only: · | *
  • —+ means OR (alternation)
  • —No [char-class] syntax
  • —No ? or {n,m} quantifiers
  • —No backreferences \1
  • —No lookahead/lookbehind
  • —No flags (i, g, m, s…)
  • —Always full-string match
  • —ε and ∅ are first-class
  • —Maps directly to DFA/NFA

Programming Regex

  • —Dozens of operators
  • —+ means one-or-more
  • —[a-zA-Z0-9] ranges
  • —?, {n,m}, lazy, possessive
  • —Backreferences \1 (?P=n)
  • —Lookahead (?=…) (?<!…)
  • —Flags modify behavior
  • —Substring search by default
  • —No ε / ∅ literals
  • —Often exceeds regular power

Who is this for?

🎓

CS Students

Test homework from automata theory and formal languages courses. Verify your DFA/NFA→RE conversion before the exam.

📖

Educators

Share live examples in lectures and problem sets. Students experiment with no setup required.

📐

Self-learners

Working through Sipser, Hopcroft–Ullman, or Kozen? Use this as a live companion to the textbook.

🔬

Researchers

Quickly validate small language examples while working on papers about regular languages.

🛠

Tool Builders

Cross-check formal language definitions against test cases before implementing a DFA.

🤔

The Curious

Want to understand the theory behind all programming regex? This is the mathematical foundation.

44 Worked Examples

Expand any card to see its test strings. Click ▶ Open or any chip to open the example in the interactive editor with full SEO-optimised URL. Each example page supports linking, sharing, and embedding.

01◆ Primitives
∅Empty set ∅
02◆ Primitives
εEmpty string ε
03◆ Primitives
aSingle literal character
04◆ Primitives
ΣΣ — any single character
05◆ Primitives
Σ*Σ* — the universal language
06· Concatenation
abConcatenation: ab
07· Concatenation
a⋅bExplicit concatenation: a⋅b
08· Concatenation
aεε is the concatenation identity: aε = a
09· Concatenation
a∅∅ annihilates concatenation: a∅ = ∅
10· Concatenation
abcThree-character string: abc
11· Concatenation
abΣ*Fixed prefix + free suffix: abΣ*
12✦ Kleene Star
a*a* — zero or more a
13✦ Kleene Star
(ab)*(ab)* — zero or more "ab" pairs
14✦ Kleene Star
(aa)*(aa)* — even-length strings of a
15✦ Kleene Star
(a*)*Star is idempotent: (a*)* = a*
16✦ Kleene Star
aΣ*baΣ*b — starts with a, ends with b
17✦ Kleene Star
aa*"One or more a": aa*
18| Alternation
a|bAlternation with |: a|b
19| Alternation
a+b+ means OR in formal RE: a+b
20| Alternation
a|∅∅ is the union identity: a|∅ = a
21| Alternation
a|εOptionality via ε: a|ε
22| Alternation
a|b|cThree-way alternation: a|b|c
23| Alternation
cat|dog|fishWord alternation: cat|dog|fish
24| Alternation
b|aAlternation is commutative: b|a = a|b
25⊕ Combined
(a|b)*a(a|b)*a — strings over {a,b} ending in a
26⊕ Combined
(a|b)*b(a|b)*b — strings over {a,b} ending in b
27⊕ Combined
(a|b)*aa(a|b)*(a|b)*aa(a|b)* — contains substring "aa"
28⊕ Combined
(b|ab)*(a|ε)(b|ab)*(a|ε) — no two consecutive a's
29⊕ Combined
ΣΣΣΣΣΣ — exactly 3 characters
30⊕ Combined
ΣΣΣ*ΣΣΣ* — length ≥ 2
31⊕ Combined
a(b|c)Distributive law: a(b|c) = ab|ac
32⟳ Classic Problems
(aaa)*(aaa)* — unary multiples of 3
33⟳ Classic Problems
aa|bbaa|bb — length-2 palindromes
34⟳ Classic Problems
a(a|b)*a|b(a|b)*b|a|bStarts and ends with the same character
35⟳ Classic Problems
(a|b)*a(a|b)(a|b)(a|b)*a(a|b)(a|b) — 3rd from end is a
36⟳ Classic Problems
(ΣΣ)*(ΣΣ)* — even-length strings
37⟳ Classic Problems
a*(ba+)*a*(ba+)* — no consecutive b's
38≠ vs Programming Regex
a+b⚠ Formal + vs programming + quantifier
39≠ vs Programming Regex
abcNo substring search — full-string only
40≠ vs Programming Regex
0|1|2|3|4|5|6|7|8|9No [char-class] — enumerate explicitly
41≠ vs Programming Regex
(a|ε)bNo ? quantifier — use (r|ε)
42≠ vs Programming Regex
Σ*aΣ*No backreferences — strictly regular
43≠ vs Programming Regex
HelloNo flags — always case-sensitive
44≠ vs Programming Regex
(a|b)*b(a|b)*No lookaheads — restructure instead

Implementation

The formal flavor runs a three-stage pipeline — parse, rewrite, execute — entirely client-side, with no external runtime. The grammar is enforced by a real recursive-descent parser (compiled to WASM, with an equivalent TypeScript fallback), not by textual substitution, so operator precedence is guaranteed by construction and invalid syntax fails with a precise, positioned error.

Stage 1 — parse & validate (recursive descent, WASM + TS fallback)
alt→ concat (('+'|'|') concat)*union — + is always alternation
concat→ star (·? star)*juxtaposition = concatenation; ⋅/· optional
star→ atom '*'Kleene star binds most tightly
atom→ Σ | ε | ∅ | literal | '(' alt ')'first-class symbols + grouping
Anything outside the grammar is rejected with a positioned error — e.g. '?' is not a formal RE operator, Unclosed group — missing ) — and the AST feeds the explanation panel's annotated tree.
// Stage 2 — normalizeFormalRegex() symbol rewrite
∅→ (?!)unconditionally failing lookahead — never matches
ε→ (?:)empty non-capturing group — matches only ε
Σ→ [^\n]any character except newline (= alphabet)
⋅→ (removed)explicit concatenation is implicit in JS
+→ |formal union → JS alternation operator
(...)→ (?:...)grouping without capture semantics
pattern→ ^(?:...)$automatic full-string anchoring + multiline flag

Stage 3 — automata execution. The rewritten pattern runs on the RE2 engine (the Rust regex crate compiled to WASM), which simulates automata instead of backtracking — matching is linear-time, so catastrophic backtracking is structurally impossible. One rewrite keeps the pattern RE2-native: ∅’s (?!) becomes the never-matching class [^\s\S]. If that engine can’t load, execution falls back to a dedicated Web Worker under a one-second watchdog that is hard-terminated on timeout with an explicit Execution timeout error — so the page never freezes either way.

Known limitations

Two items from the original version of this post have since been resolved — the operator-precedence parser shipped, and catastrophic backtracking is now eliminated: formal patterns execute on a linear-time automata engine. What remains is listed below with its current status.

Catastrophic backtracking

Eliminated

Originally formal patterns executed on the backtracking JavaScript engine (contained only by a watchdog). They now run on the RE2 automata engine (the Rust regex crate compiled to WASM), which matches in linear time — adversarial patterns like (a*)*b on non-matching input, which could exhaust any time budget under backtracking, now complete in microseconds. If the RE2 wasm can't load (blocked network, no Worker support), execution falls back to the JS engine inside a dedicated Web Worker that is hard-terminated on timeout — degraded to 'contained' for that session, never a frozen tab.

Operator characters cannot be literals

Parser shipped

Formal RE has no escape syntax — a stray backslash is rejected with a positioned error ('\' is not a formal RE operator). So +, |, *, (, ), Σ, ε, and ∅ always act as operators or symbols; an alphabet containing + as a literal character cannot be expressed. (An earlier version of this post described the + → | rewrite as a text substitution and promised a full operator-precedence parser 'for a future update' — that parser has since shipped, see the Implementation section above, so precedence is now enforced by the grammar itself.)

Complement is not a formal RE operator

The complement of a regular language (¬L) is regular, but complement is not part of formal RE syntax. You cannot write ¬(ab*) — you must construct the complementary RE directly, which can be exponentially larger.

Non-regular patterns cannot be expressed

By design

Balanced parentheses, arbitrary-length palindromes, and {aⁿbⁿ | n≥0} are not regular languages. No formal RE can express them. This is a feature — it reflects the true power boundary of regular languages.