Lua Pattern Tester
Runs a real Lua 5.4 VM in your browser. Lua patterns are not regular expressions — a|b is a literal, and %b / %f have no regex equivalent.
timeout = 30 retries = 5 host = "localhost"
Lua pattern syntax
- %a %A
- Letter / not a letter
- %d %D
- Digit / not a digit
- %s %S
- Whitespace / not whitespace
- %w %W
- Alphanumeric / not alphanumeric
- %l %u
- Lowercase letter / uppercase letter
- %p %c %x
- Punctuation / control character / hex digit
- .
- Any character
- [set] [^set]
- Character in set / not in set
- *
- Zero or more, greedy
- +
- One or more, greedy
- -
- Zero or more, lazy (regex *?)
- ?
- Zero or one
- ^ $
- Anchor at start / at end
- ( )
- Capture
- ()
- Position capture — returns an offset
- %1 %2
- Backreference, in the replacement string only
- %bxy
- Balanced match from x to y, counting nesting
- %f[set]
- Frontier — empty match at a set boundary
- %% %.
- Literal percent / literal dot (escape with %)
The Lua VM runs in a Web Worker in your browser. No pattern or subject text is sent to any server.
About this tool
Lua patterns are not regular expressions
Lua deliberately ships a matching language rather than a regular expression engine, and the whole implementation is roughly two hundred lines of C. Knowing what it leaves out saves hours of confusion. There is no alternation, so the pattern a|b matches the literal three-character sequence and nothing else; to match either word you run two matches or use a character class when the alternatives are single characters. There are no non-capturing groups, no lookahead or lookbehind, no named captures, no in-pattern backreferences, no repetition counts such as {2,5}, and no flags for case insensitivity or multiline mode. Escaping uses a percent sign instead of a backslash, which trips up anyone arriving from another language: a literal dot is %., a literal percent is %%, and a literal open bracket is %[. The quantifiers are also fewer and one of them is unusual: * and + are greedy as expected, ? is zero-or-one, and - is the lazy zero-or-more that other languages spell *?. There is no lazy form of + at all. What Lua adds in return is %b for balanced delimiters and %f for frontier assertions, neither of which regular expressions can express without extensions.
Why this runs a real Lua VM
Reimplementing Lua's matcher in JavaScript is the obvious shortcut and the wrong one, because the details that matter are exactly the ones a reimplementation gets wrong. The behaviour of %b when delimiters nest, the empty-width semantics of %f at the start and end of a subject, position captures returning an offset rather than text, the way an anchored ^ pattern binds to the init offset given to string.find rather than to the start of the string, and the precise wording of errors like malformed pattern (missing ']') or invalid capture index %3 all come free from the real implementation and are all easy to get subtly wrong by hand. So this tester compiles the actual Lua 5.4 interpreter to WebAssembly and calls string.find and string.gsub on it. Your pattern and subject are passed as argument values rather than spliced into Lua source, so a pattern cannot inject code, and the string pattern syntax has not changed between Lua 5.1 and 5.4 — only the %g class was added, in 5.2 — which means results here apply to LuaJIT and OpenResty too.
Related tools: JSON to Lua, Lua to JSON
Frequently Asked Questions
Are Lua patterns the same as regular expressions?
No, and assuming they are is the most common source of bugs. Lua patterns are a small, deliberately simple matching language implemented in about 200 lines of C, not a regular expression engine. The differences bite immediately. There is no alternation: the pattern a|b matches the literal three-character string "a|b", not "a" or "b". There are no non-capturing groups, no lookahead or lookbehind, no named captures, no backreferences inside the pattern itself, no {n,m} repetition counts, and no case-insensitive flag. Escaping uses a percent sign rather than a backslash, so a literal dot is %. and a literal percent is %%. Character classes are single letters after a percent: %a for letters, %d for digits, %s for whitespace, %w for alphanumerics, and the uppercase form of each is its complement. In exchange for what it gives up, Lua adds two things regex does not have: %b for balanced delimiter matching and %f for frontier assertions. This tester runs your pattern through a real Lua 5.4 virtual machine, so what you see here is exactly what your Lua code will do.
What do %b and %f do?
These are the two features Lua patterns have that regular expressions lack, and both are genuinely hard to replicate elsewhere. %bxy matches a balanced run of text starting with character x and ending with the matching character y, counting nesting as it goes. So %b() applied to the string f(a(b)c)d matches (a(b)c) in full rather than stopping at the first closing parenthesis, which is what makes it the right tool for extracting nested brackets, braces, or parenthesised expressions — something a regular expression fundamentally cannot do without recursion extensions. %f[set] is a frontier pattern: it matches the empty string at any position where the previous character is not in the given set and the next character is. It behaves like a generalised word boundary, so %f[%a]%a+%f[%A] matches whole words without consuming the characters on either side. Because a frontier matches empty, it never advances the position by itself, which makes it safe to combine with other patterns.
Why did my pattern time out?
Because it was still running after the time limit, and the tool killed it rather than let it freeze your browser tab. Lua patterns cannot suffer the exponential catastrophic backtracking that regular expressions can, since they have no alternation, but they can still be far worse than linear. The shape to watch for is several unbounded quantifiers in sequence: a pattern such as .-.-.-.-.-c makes each quantifier scan the entire remaining subject for every position the one before it tries, which multiplies out very quickly. On a few thousand characters that pattern runs for minutes. Even a single nested pair like (%a*)*c costs time proportional to the square of the subject length. The fix is almost always to bound the quantifiers instead of leaving them open: replace .- with a negated class that cannot cross the delimiter you care about, so <(.-)> becomes <([^>]*)>. That change alone usually turns a pathological pattern into a linear one. Shortening the subject also helps, since the cost scales with its length.
Does my text get sent to a server?
No. The Lua virtual machine is compiled to WebAssembly and runs inside a Web Worker in your own browser, so both the pattern and the subject text stay on your machine. There is no API call, no logging, and nothing to opt out of, which means the tool is safe to use on production log lines, configuration files, and other text you would not paste into a hosted service. The worker exists for a second reason beyond privacy: because Lua's matcher is C code inside the WebAssembly module, a long-running match cannot be interrupted from inside Lua at all — an instruction-count debug hook never fires, because no Lua instructions execute while the C matcher runs. Terminating the worker from the main thread is the only reliable way to stop it, and that is only possible because the virtual machine lives off the main thread. Your pattern and subject cross into Lua as argument values rather than being concatenated into Lua source, so a pattern cannot inject code.
Standards & References
- Lua 5.4 Reference Manual §6.4.1 — Patterns
- string.gsub — Replacement semantics and %0–%9