Delta logo
Guide

Thompson's Construction

thompson() builds NFAs from regular expressions using a postfix stack-based approach. Instead of wiring states manually, you push primitive machines onto a stack and combine them with operators — exactly the way Thompson's construction algorithm works on paper.

How It Works

The builder maintains an internal stack of NFA fragments. Primitive operations push a new fragment; combining operations pop fragments, merge them, and push the result. .build() pops the final (only remaining) fragment and returns it as an NFA.

import { thompson } from 'delta:lib'
 
const machine = thompson('name')
  // push primitives...
  // apply operators...
  .build()

Primitives

MethodPushes
.char(s)A two-state NFA accepting exactly the single character s
.eps()A two-state NFA accepting the empty string (ε)
.machine(nfa)An existing NFA object (useful for composing pre-built machines)

Operators

Each operator pops one or two fragments from the stack and pushes the combined result.

MethodPopsResult
.union()2Accepts strings accepted by either fragment (A | B)
.concat()2Accepts strings formed by concatenating the two fragments (AB)
.star()1Accepts zero or more repetitions of the fragment (A*)

The order matters for binary operators: the first pushed fragment is the left operand.

Stack Discipline

The stack must have exactly one fragment remaining when .build() is called. Too few elements for an operator, or leftover fragments at build time, throw an UnexpectedStackError.

RegexBuilder calls
ab.char("a").char("b").concat()
a|b.char("a").char("b").union()
a*.char("a").star()
(ab)*.char("a").char("b").concat().star()
a*(b|c).char("a").star().char("b").char("c").union().concat()

Composing Machines

.machine(nfa) lets you push a fully built NFA as a sub-expression. Build each clause separately, then combine them:

const clause1 = thompson('A')./* ... */.build()
const clause2 = thompson('B')./* ... */.build()
 
const combined = thompson('A | B')
  .machine(clause1)
  .machine(clause2)
  .union()
  .build()

State names in composed machines are automatically renumbered to avoid collisions.

Converting to a DFA

The output of thompson() is always an ε-NFA. To obtain a deterministic machine, use convertToDFA — also available from delta:lib:

import { thompson, convertToDFA } from 'delta:lib'
 
const nfaMachine = thompson('name')./* ... */.build()
const dfaMachine = convertToDFA(nfaMachine, { name: 'my DFA', preserveNames: false })
 
export default dfaMachine