Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
429 commits
Select commit Hold shift + click to select a range
3ab2029
Clarification, resolving a few TODOs, smaller CLI options etc.
katef Jun 6, 2024
37d34c8
Allocate and duplicate for \0-terminated patterns, rather than pointi…
katef Jun 7, 2024
aa0eca1
Simplification, deal with -n earlier.
katef Jun 7, 2024
b6fe284
Give an example for AMBIG_ERROR.
katef Jun 19, 2024
7290f01
Move declined-file to an optarg, we need to keep argv[] for multiple …
katef Jun 19, 2024
798f8e7
Store patterns per set, rather than indexing into a global pattern ar…
katef Jun 20, 2024
41d8225
Check end id ambiguity before printing, rather than during.
katef Jun 20, 2024
a6deb96
No need for the *nwritten parameter.
katef Jun 20, 2024
4e5cf35
Switch to reading and allocating line-by-line, rather than mmap().
katef Jun 21, 2024
341dfc8
C99 doesn't allow a different type qualifier here, only C2x.
katef Jun 21, 2024
9779714
Multi-file input.
katef Jun 22, 2024
b436b81
Skip re_is_literal()'s parsing an AST construction for literals.
katef Jun 24, 2024
d546585
Override dialect by file extension.
katef Jun 24, 2024
6d2f1f3
-p for prefix.
katef Jun 24, 2024
45e3cfc
-F for re_flags.
katef Jun 24, 2024
c7d242f
No need to handle endids during fsm_minimise here.
katef Jun 24, 2024
53164ae
Don't call intersect_charset() with an empty charset, it's clearer to…
katef Jun 24, 2024
4232303
Comment generated code
katef Jun 25, 2024
ff51a75
Codegen for various languages.
katef Jun 25, 2024
4aa804a
-x for unanchored literals.
katef Jun 25, 2024
e7fd0f4
Include num_re_errors in per-file failures.
katef Jun 28, 2024
e66e6cf
Skip an IO API/lang combination that we don't support.
katef Jun 28, 2024
b0d1de6
Quieten GNU ld about something I don't understand.
katef Jun 28, 2024
14bbc0f
Merge pull request #480 from katef/kate/retest-errors
katef Jun 28, 2024
d756f9e
Sort endids written out by fsm_endid_get().
katef Jul 1, 2024
f35887d
Merge pull request #482 from katef/kate/sort-endids
katef Jul 1, 2024
093b703
First cut at introducing llvm ir codegen.
katef Jun 27, 2024
2371f42
-opaque-pointers for clang 14 in github CI.
katef Jun 28, 2024
09e1299
Maintain n index from 0, not -1.
katef Jun 29, 2024
d49de86
We need -mllvm for opaque pointers as well as -opaque-pointers.
katef Jun 29, 2024
f6e7ef6
Run these retest tests for llvm too.
katef Jun 29, 2024
2a6445d
Consistency for print_label()
katef Jun 29, 2024
13f93dd
Whitespace.
katef Jun 30, 2024
73e4074
Factor out print_branch()
katef Jun 29, 2024
2c31f63
Centralise `fail:` nodes.
katef Jun 30, 2024
8697e98
Construct retlist and output deduped rets.
katef Jun 30, 2024
b11d542
Branch to centralised retlist labels.
katef Jul 1, 2024
0ae60db
Jump via ret*: labels to a single stop:
katef Jul 1, 2024
49118f5
An arguably less worse workaround to keep .endleaf_opaque available.
katef Jul 4, 2024
6b76c46
Merge pull request #483 from katef/kate/llvm
katef Jul 4, 2024
0b84d75
Centralise fsm_print() and friends.
katef Jul 6, 2024
481af9f
Missing targets.
katef Jul 10, 2024
0adad69
Hoist up ir and vm opcode compilation to fsm_print().
katef Jul 10, 2024
28c8558
Return bool for match/failure from generated code.
katef Jul 12, 2024
97ec4e3
No need for ir_state here.
katef Jul 12, 2024
1545d1c
Only qsort a non-empty retlist.
katef Jul 18, 2024
eef6c00
Introduce ambig enum, and replace leaf/endleaf callbacks with reject/…
katef Jul 18, 2024
b4538cf
Cruft.
katef Jul 19, 2024
61fb10e
Move alloc hooks to struct fsm.
katef Jul 19, 2024
a7d6847
Uninitialized value.
katef Jul 20, 2024
4b2ab25
Remove fsm.opt and split print hooks from options.
katef Jul 21, 2024
9483c50
fsm_dump(), a convenience to replace the original fsm_print() for deb…
katef Jul 22, 2024
2b9b154
Port CLI tools to use opt.ambig and the conflict callback.
katef Jul 26, 2024
e03de6b
Clarification.
katef Jul 26, 2024
a564db1
-s for overriding dialect by file extension.
katef Jul 26, 2024
18b2d73
-e and -E for prefixes.
katef Jul 27, 2024
366fce6
Move out retlist construction.
katef Jul 27, 2024
684433c
Constuct retlist ahead of time for all vm-based formats.
katef Jul 28, 2024
5092212
Add AMBIG_MULTIPLE output for go.
katef Jul 28, 2024
7cdb00b
No need for the vmops_dialect enum here.
katef Jul 31, 2024
7e970d7
Whitespace.
katef Jul 31, 2024
f2bae91
Bugfix; 1 means VM_END_SUCC here.
katef Aug 2, 2024
ac4a66a
Ensure we don't pass NULL to memcmp()
katef Aug 2, 2024
436d6e0
Typo.
katef Aug 2, 2024
e484294
Support for enids for the vmops output.
katef Aug 2, 2024
62d3b3b
Merge pull request #485 from katef/kate/ambig-mode
katef Aug 4, 2024
2698127
First cut at AMBIG_MULTIPLE for llvm.
katef Aug 5, 2024
7426501
Rework stop: to index into an array for return values.
katef Aug 6, 2024
f69dbfc
Don't use `undef` for values the caller is expected to not access, us…
katef Aug 6, 2024
292fd1c
Split overriding comments to a separate hook.
katef Aug 7, 2024
bb4e81d
No need to emit a bitmap here.
katef Aug 7, 2024
6c17934
Use id count to indicate match failure, and >= 0 means success.
katef Aug 7, 2024
c44bfd2
Clarification.
katef Aug 7, 2024
ba70069
Add AMBIG_MULTIPLE output for Rust.
katef Aug 7, 2024
218e90c
Factor out print_ret().
katef Aug 7, 2024
021d169
Merge branch 'main' into kate/more-multi
katef Aug 7, 2024
e6950b8
Merge branch 'kate/more-multi' into kate/rx
katef Aug 9, 2024
719b1b2
Whoops... wrong exit status.
katef Aug 11, 2024
690f0e2
Oops... this should've been done with the API change to return a bool.
katef Aug 12, 2024
0f4d838
Separate calling points for the comments hook.
katef Aug 14, 2024
ab0a411
Typo.
katef Aug 16, 2024
0a36f1b
Merge pull request #486 from katef/kate/more-multi
katef Aug 16, 2024
a435074
Merge branch 'main' into kate/rx
katef Aug 16, 2024
f0fea27
-t for AMBIG_EARLIEST
katef Aug 16, 2024
26832cc
Graphviz output for the DFA.
katef Aug 16, 2024
d062ec9
-a and -w for anonymous states and fragment output.
katef Aug 16, 2024
5e10440
-X for always_hex
katef Aug 16, 2024
7607456
-Fb for consistency wrt -Fi.
katef Aug 17, 2024
a925708
Documentation for -F flags and -k io api.
katef Aug 17, 2024
c26c5fd
A little more progress on invocation.
katef Aug 17, 2024
64f26cc
Merge pull request #487 from katef/kate/man-flags
katef Aug 17, 2024
9668a76
Manpage for rx(1)
katef Aug 17, 2024
c08782a
Missing flag, this is now present on main.
katef Aug 20, 2024
d73682b
Stray assertion.
katef Aug 20, 2024
8c9b209
Spelling.
katef Aug 22, 2024
5fa91fe
In `closure_free()`, use the FSM allocator instead of `free()`.
sw17ch Aug 23, 2024
98fe88b
Merge pull request #489 from sw17ch/sw17ch/fix-closure-free-alloc
katef Aug 23, 2024
84f3835
Wrong exit status for -G.
katef Aug 24, 2024
60baaab
Wrong return type for AMBIG_ERROR/AMBIG_EARLIEST.
katef Aug 24, 2024
e7a21b4
-C opt-level=3 makes a huge difference here.
katef Aug 24, 2024
edeb291
Missing language.
katef Aug 24, 2024
3769f77
Off by one.
katef Aug 24, 2024
00ad53b
Bump actions for Node 20.
katef Aug 24, 2024
d557943
Happier cache lines.
katef Aug 24, 2024
6a40553
Guard .tag; it helps keep structs tidy.
katef Aug 25, 2024
f1ca8e8
No need for calloc here.
katef Aug 25, 2024
5010a40
Stray assertion.
katef Aug 24, 2024
ebbcb35
Update for hooks & options API changes.
katef Aug 26, 2024
632ddd3
Merge pull request #490 from katef/kate/bugfixin
katef Aug 26, 2024
8f9ea4d
Spelling.
katef Aug 27, 2024
dc9721f
Merge pull request #488 from katef/kate/rx
katef Sep 5, 2024
81b14f8
Merge pull request #491 from katef/kate/happy-cache-lines
katef Sep 5, 2024
981223f
bitmap.h: Add operations: copy, intersect, union, any, unset..
silentbicycle May 21, 2024
fa24439
Add fsm_detect_required_characters.
silentbicycle May 21, 2024
6c61249
bugfix: fsm_endid_get should sort with result count, not buffer size.
silentbicycle Sep 12, 2024
dec6ec0
endids_reused_buffer.c: Fix memory leak in test. (free fsm.)
silentbicycle Sep 12, 2024
521789f
Merge pull request #493 from katef/sv/bugfix-endid-qsort-count
katef Sep 13, 2024
5705d43
Update to Unicode 16.0
data-man Sep 17, 2024
0f0dbb6
Merge pull request #495 from data-man/ucd16
katef Sep 17, 2024
9aa1b9a
fsm_compact_states must remap endids, to avoid dangling references.
silentbicycle Sep 17, 2024
3295240
Change fsm_detect_required_characters interface.
silentbicycle Sep 18, 2024
5dcdeb6
fsm_detect_required_characters: Set count to 0 when matching "".
silentbicycle Sep 19, 2024
208de5f
Move detect_required1.c tests to .re/.txt files. Add -q chars to fsm.
silentbicycle Sep 19, 2024
aab4422
fsm.1.xml: Add basic info about -S <step_limit> and -q requiredchars.
silentbicycle Sep 19, 2024
4758734
print API: Box end_ids and end_id_count in a struct for callbacks.
silentbicycle Sep 23, 2024
8487fcb
Move call to fsm_countstates inside #ifndef NDEBUG.
silentbicycle Sep 23, 2024
2d61cf9
libfsm.syms: Expose fsm_new_statealloc.
silentbicycle Sep 23, 2024
1295005
ir: Wrap ids and count in a struct, to namespace them as endids.
silentbicycle Sep 24, 2024
eb576b8
Merge pull request #492 from katef/sv/determine-which-characters-must…
katef Sep 25, 2024
2474c51
Merge pull request #496 from katef/sv/bugfix-compact-states-should-re…
katef Sep 26, 2024
0961848
Merge pull request #497 from katef/sv/move-codegen-callback-metadata-…
katef Sep 27, 2024
e3e868f
Temporary interface: Add fsm_determinise_with_config.
silentbicycle Oct 9, 2024
07e1a0c
Add 'default:' (should be unreachable) for switch case.
silentbicycle Oct 10, 2024
bfeb7ce
Merge pull request #498 from katef/sv/determinise-with-config
katef Oct 10, 2024
a61a9da
fsm_generate_matches: Add seed argument, random choice if non-zero.
silentbicycle Oct 9, 2024
ffe6f69
common.h: TRACK_TIMES should be 0 when BUILD_FOR_FUZZER is set.
silentbicycle Sep 11, 2024
b2f9d3d
determinise: Remove stale comment.
silentbicycle Sep 10, 2024
35892eb
trim's integrity_check should only run with EXPENSIVE_CHECKS.
silentbicycle Sep 9, 2024
cf8fc65
Remove direct call to srand(), just recommend the caller use it.
silentbicycle Oct 11, 2024
c375229
Merge pull request #499 from katef/sv/randomized-generation-and-misc-…
katef Oct 12, 2024
92e6e82
Add re_is_anchored interface.
silentbicycle Sep 16, 2024
09a917a
Merge pull request #500 from katef/sv/re-is-anchored
katef Oct 12, 2024
98dfb2d
Missing documentation for -x
katef Oct 23, 2024
280ca7c
Too much documentation for -u.
katef Oct 23, 2024
6dc7305
Merge pull request #502 from katef/kate/missing-manpage-flag
katef Oct 23, 2024
7ebc1eb
Fix memory leak.
silentbicycle Nov 7, 2024
c9a240a
Add a regression test for the memory leak.
silentbicycle Nov 8, 2024
3cf9ee7
Merge pull request #504 from katef/sv/fix-memory-leak-from-determinis…
katef Nov 12, 2024
3572258
Update CI to use Ubuntu 22.04 for now so we do not get bleeding edge …
deg4uss3r Feb 11, 2025
db6dcdd
Merge pull request #505 from deg4uss3r/rth/fix-ci
katef Mar 21, 2025
c89c15d
retest: Remove `isatty` check and extra logging output.
silentbicycle Jun 17, 2025
f6fc836
Merge pull request #506 from katef/sv/remove-isatty-check
katef Jun 19, 2025
825303f
Remove dependence on internal.h in print/ir.h and lx/print/c.c.
silentbicycle Jul 3, 2025
cb42d58
Makefile: Check '*res*' not 'res*' for tests.
silentbicycle Jul 8, 2025
7824e6a
Fix lx by rewriting much of its -l c codegen.
silentbicycle Jul 3, 2025
26e2b13
test/lxpos: Update test data (EOF position info).
silentbicycle Jul 22, 2025
862a68c
Re-enable lxpos tests.
silentbicycle Jul 22, 2025
affca78
Use $LX_BIN instead of $LX in lxpos makefile.
silentbicycle Jul 23, 2025
a8f0c59
lx: Make -l dump's output call lx.free() when using dynamic buffer.
silentbicycle Jul 23, 2025
552aa01
lx: Use prefix.tok, not "TOK_".
silentbicycle Jul 29, 2025
beebd1b
lx: return TOK_ERROR if reaching the end of a zone function.
silentbicycle Jul 29, 2025
ae53e94
lx: Handle unexpected EOF in pattern pairs. Add tests, update out6.dump.
silentbicycle Jul 29, 2025
ea9c90b
lx: Rewrite logic to make the four cases explicit, fix dead code.
silentbicycle Jul 30, 2025
17d415d
lx: Only gen fixedpop / dynpop & calls to them when buffer mode is set.
silentbicycle Jul 31, 2025
7ed18b9
lx: Suppress warning for possibly unused function.
silentbicycle Jul 31, 2025
1e55db8
lx: Ensure prefix.api & prefix.lx are used in the generated code.
silentbicycle Jul 31, 2025
4a5ca84
Replace FSM_ADVANCE_HOOK macro with optional hooks->advance callback.
silentbicycle Aug 4, 2025
f25e8b7
The advance hook should also be called for FSM_IO_STR.
silentbicycle Aug 4, 2025
051aaf0
Move setting `has_consumed_input` flag into lx's advance hook.
silentbicycle Aug 4, 2025
08fd72c
lx: Avoid useless call to pop and some other 'unused' warnings.
silentbicycle Aug 18, 2025
c897e9d
Merge pull request #509 from katef/sv/fix-lx-token-identification
katef Aug 20, 2025
c853157
lx: Distinguish between unexpected EOF and EOF in ignored zones.
silentbicycle Aug 26, 2025
9be4aec
Generated code. Re-generate lexers and parsers with lx bug fixed.
silentbicycle Aug 26, 2025
051c362
Typo.
katef Aug 28, 2025
87f6df2
Stray const.
katef Aug 28, 2025
400c0a5
Merge pull request #511 from katef/kate/stray-const
katef Aug 28, 2025
6c66234
Merge pull request #510 from katef/sv/fix-lx-handling-for-EOF-broken-…
katef Aug 29, 2025
664b32c
Update to Unicode 17.0
data-man Sep 10, 2025
4155d56
experimental: Add eager outputs, similar to endids but eagerly matched.
silentbicycle Aug 27, 2024
74907ca
Ensure .has_eager_outputs is zeroed on new states. (msan)
silentbicycle Oct 10, 2024
f2ddf1d
eager_output interface cleanup: Replace _any with _count and _get.
silentbicycle Oct 10, 2024
981128f
minimise_test_oracle.c: mismatched eager outputs also prevent merging.
silentbicycle Oct 10, 2024
fa63fcd
fuzz/target.c: re_is_anchor interface changes.
silentbicycle Oct 12, 2024
496198d
fuzzer: Add seed argument for fsm_generate_matches (interface change).
silentbicycle Oct 10, 2024
e2b9130
Fix memory leak in fsm_eager_output_compact, found while fuzzing.
silentbicycle Jan 31, 2025
36d0187
Fix fsm_union_repeated_pattern_group's anchoring linkage.
silentbicycle Feb 4, 2025
af27a87
fsm_union_repeated_pattern_group: Interface changes.
silentbicycle Feb 4, 2025
d44a671
fsm_union_repeated_pattern_group: fix linkage for mixed start anchoring.
silentbicycle Feb 5, 2025
36129d0
Add tests, fix anchoring bugs in fsm_union_repeated_pattern_group.
silentbicycle Feb 12, 2025
7b8b169
Interface change: Add 'const'.
silentbicycle Feb 12, 2025
6b07bd2
union: Fix trivial memory leak.
silentbicycle Feb 12, 2025
7546f81
union.c: Add comments for assertions.
silentbicycle Feb 12, 2025
bb9f620
Switch to collecting an anchored_start state set, not just one state.
silentbicycle Feb 14, 2025
a54095a
Rename test file -- make tests looks for build files matching "*res*".
silentbicycle Sep 19, 2025
eca233d
union.c: Updates to fsm_union_repeated_pattern_group and its internals.
silentbicycle Sep 9, 2025
e712575
Eager outputs: Comment on use, rename some functions, clean up.
silentbicycle Sep 15, 2025
eeea923
fuzz/target.c: Updates for interface changes.
silentbicycle Sep 19, 2025
d6db021
Misc. cleanup before integration.
silentbicycle Sep 19, 2025
3293b7b
Restore FORCE_ENDIDS behavior for tests/eager_output/eager_output7.c.
silentbicycle Sep 19, 2025
255e426
fsm.h: Fix missing word in comment.
silentbicycle Sep 22, 2025
610f2a3
Note why fsm_union_repeated_pattern_group depends on re_comp.
silentbicycle Sep 22, 2025
68c612d
Merge pull request #512 from data-man/ucd17
katef Sep 23, 2025
6f4288e
Merge branch 'main' into sv/eager-outputs-and-union-repeated-pattern-…
silentbicycle Oct 16, 2025
a32aff3
Stray comment.
katef Oct 28, 2025
fcde3b7
Optionally save linkage_info during NFA construction in re_comp.
silentbicycle Oct 16, 2025
9971fe1
Copy some fields from linkage_info, remove analysis.
silentbicycle Nov 5, 2025
50a5fe7
Restore freeing of state sets copied from linkage_info.
silentbicycle Nov 5, 2025
c65d68c
Update stale comment.
silentbicycle Nov 20, 2025
504b1d0
Merge pull request #513 from katef/sv/eager-outputs-and-union-repeate…
katef Nov 20, 2025
8debf24
Add documentation guide on how to use Libfsm effectively
gsusanto-fastly Nov 25, 2025
7306570
Update docs based on reviews
gsusanto-fastly Nov 27, 2025
20f262c
Revision #2
gsusanto-fastly Nov 27, 2025
227f4ac
Naming.
katef Nov 27, 2025
c4d4ffe
Markup.
katef Nov 27, 2025
bf867f2
Blurb on calling the generated code.
katef Nov 27, 2025
4351273
Blurb on bounded repetition.
katef Nov 27, 2025
d8ab92e
Markup.
katef Nov 27, 2025
27802dc
Merge pull request #515 from katef/gsusanto-docs
katef Nov 27, 2025
e6683da
First cut at re_interpolate_groups()
katef Jan 16, 2026
2f0c772
Add start,end error reporting
katef Jan 26, 2026
1a6f007
Clarification.
katef Jan 26, 2026
cf2bb0a
Fill out placeholders for writing out output.
katef Jan 26, 2026
3234a7c
Convincing myself string offsets are convenient
katef Jan 26, 2026
433c8b8
Allow a NULL output string.
katef Jan 26, 2026
4ae257e
Clarification.
katef Jan 26, 2026
0d487a6
Defensively terminate the output buffer on error.
katef Jan 27, 2026
8247320
Update to actions/cache@v5
katef Jan 27, 2026
6d1bd9b
fail-on-cache-miss: for grabbing arbitrary builds.
katef Jan 27, 2026
c1203e3
Explicitly allow build cache miss for makefile tests.
katef Jan 27, 2026
3f69a7c
Explicitly fail-on-cache-miss for other things too.
katef Jan 27, 2026
661105e
cache/restore where possible.
katef Jan 27, 2026
74ab9a7
Merge branch 'kate/actions-fluffery' into kate/interpolate_groups
katef Jan 27, 2026
d817464
Merge pull request #517 from katef/kate/actions-fluffery
katef Jan 27, 2026
a1526ab
Merge pull request #516 from katef/kate/interpolate_groups
katef Jan 27, 2026
c798ec1
Merge mishap, accidentally @v4
katef Jan 27, 2026
a6e9c2a
Makefile: grep for 'FAIL' should use -I to ignore binary files.
silentbicycle Feb 9, 2026
97cdb4e
Merge pull request #519 from katef/sv/grep-test-ignore-binary-files
katef Feb 9, 2026
7ea5421
Oops... missed installing <re/groups.h>
katef Feb 10, 2026
f08f376
SEE LOG. Merge branch 'sv/integration-target--combinable-DFA-capture-…
silentbicycle May 29, 2026
d376df6
Regen lx parser: call re_comp with `| RE_NOCAPTURE`.
silentbicycle Jun 5, 2026
c69578f
captest.c: Instead of using `captest_getc` just use `fsm_sgetc`.
silentbicycle Jun 5, 2026
335cf58
Consolidate AST_ANALYSIS_ERROR_UNSUPPORTED_* cases and RE_EUNSUP*.
silentbicycle Jun 5, 2026
39bb936
Update native parser.sid: '$' is `ast-make-anchor-end-nl`.
silentbicycle Jun 5, 2026
5766444
Regenerate the parser for the native dialect.
silentbicycle Jun 5, 2026
e34627e
ast_analysis: Fix ALT reverse anchoring case, likely merge error.
silentbicycle Jun 8, 2026
1855f7b
Fix UB warning for signed int promotion with the negated flags.
silentbicycle Jun 8, 2026
5478053
Update test case: We can handle this correctly now.
silentbicycle Jun 9, 2026
a996677
ast_make_expr_alt: Ensure field is init'd to 0.
silentbicycle Jun 17, 2026
7499291
re_capvm_compile.c: Comment changes:
silentbicycle Jun 17, 2026
ee0b2f9
Bugfix: Only check flag on union's alt if it's an AST_EXPR_ALT.
silentbicycle Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
186 changes: 107 additions & 79 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ PKG += libtheft
.if !defined(NODOC)
SUBDIR += man/fsm.1
SUBDIR += man/re.1
SUBDIR += man/rx.1
SUBDIR += man/lx.1
SUBDIR += man/fsm_print.3
SUBDIR += man/libfsm.3
Expand Down Expand Up @@ -101,26 +102,30 @@ SUBDIR += src/libre/print
SUBDIR += src/libre
SUBDIR += src/fsm
SUBDIR += src/re
SUBDIR += src/rx
SUBDIR += src/retest
SUBDIR += src/lx/print
SUBDIR += src/lx
SUBDIR += src
SUBDIR += tests/capture
SUBDIR += tests/complement
SUBDIR += tests/gen
SUBDIR += tests/idmap
SUBDIR += tests/intersect
#SUBDIR += tests/ir # XXX: fragile due to state numbering
SUBDIR += tests/eclosure
SUBDIR += tests/equals
SUBDIR += tests/subtract
SUBDIR += tests/detect_required
SUBDIR += tests/determinise
SUBDIR += tests/eager_output
SUBDIR += tests/endids
SUBDIR += tests/epsilons
SUBDIR += tests/fsm
SUBDIR += tests/glob
SUBDIR += tests/like
SUBDIR += tests/literal
# FIXME: commenting this out for now due to Makefile error
#SUBDIR += tests/lxpos
SUBDIR += tests/lxpos
SUBDIR += tests/minimise
SUBDIR += tests/native
SUBDIR += tests/pcre
Expand All @@ -130,6 +135,8 @@ SUBDIR += tests/pcre-flags
SUBDIR += tests/pcre-repeat
SUBDIR += tests/pred
SUBDIR += tests/re_literal
SUBDIR += tests/re_strings
SUBDIR += tests/regressions
SUBDIR += tests/reverse
SUBDIR += tests/trim
SUBDIR += tests/union
Expand All @@ -140,6 +147,7 @@ SUBDIR += tests/sql
SUBDIR += tests/queue
SUBDIR += tests/aho_corasick
SUBDIR += tests/retest
SUBDIR += tests/re_interpolate_groups
SUBDIR += tests
.if make(theft) || make(${BUILD}/theft/theft)
SUBDIR += theft
Expand Down Expand Up @@ -183,6 +191,6 @@ STAGE_BUILD := ${STAGE_BUILD:Nbin/cvtpcre}

.if make(test)
.END::
grep FAIL ${BUILD}/tests/*/res*; [ $$? -ne 0 ]
grep -I FAIL ${BUILD}/tests/*/*res*; [ $$? -ne 0 ]
.endif

6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@
; re -cb -pl dot '[Ll]ibf+(sm)*' '[Ll]ibre' | dot
![libfsm.svg](doc/tutorial/libfsm.svg)

libfsm is not a drop-in replacement for other regex engines, and it only supports patterns that can be compiled to deterministic FSMs. In return, supported patterns run in linear time.

Getting started:

* See the [tutorial introduction](doc/tutorial/re.md) for a quick overview
of the re(1) command line interface.
* [Compilation phases](doc/tutorial/phases.md) for typical applications
which compile regular expressions to code.
* [Advice on using libfsm](doc/advice.md) for suggestions around compilation time, unsupported features, common usage patterns, and examples.

You get:

* libfsm — library for manipulating FSM (NFA and DFA)
* libre — library for compiling regular expressions to NFA
* fsm(1) — command line interface for FSM
* re(1) — command line interface for executing regular expressions
* re(1) — command line interface for regular expressions
* rx(1) — command line interface for compiling sets of regular expressions
* lx(1) — lexer generator

lx is an attempt to produce a simple, expressive, and unobtrusive
Expand Down
276 changes: 276 additions & 0 deletions doc/advice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
# Advice on using libfsm for high-performance pattern matching

libfsm compiles regular expressions to deterministic finite state machines (FSMs) and generates executable code. FSM-based matching runs in **linear time O(n)** with **no backtracking**.

Regex engines like PCRE use backtracking to explore multiple possible match paths at **runtime**.
This means the same pattern can have different execution costs depending on the input.

libfsm instead resolves all match decisions at **compile time** by constructing a Deterministic Finite Automaton (DFA).
At runtime, matching is a single linear pass over the input with no alternative paths to explore.

As a result, libfsm avoids input-dependent slowdowns and is not susceptible to regular expression–based denial-of-service (ReDoS) attacks.

**libfsm is not a drop-in replacement for traditional regex engines.** It only supports patterns that can be compiled to FSMs.

### **Topics**

- [What libfsm Cannot Do](#what-libfsm-cannot-do)
- [Quick Start](#quick-start)
- [Supported Code Generation Targets](#supported-code-generation-targets)
- [Workflow Overview](#workflow-overview)
- [Writing Effective libfsm Patterns](#writing-effective-libfsm-patterns)
- [Byte Search Optimization](#byte-search-optimization-optional)
- [Troubleshooting](#troubleshooting)
- [Pattern Matches Empty String Unintentionally](#pattern-matches-empty-string-unintentionally)

## What libfsm Cannot Do

These PCRE features will not compile:

* Word boundaries (`\b`)
* Non-greedy quantifiers (`*?`, `+?`, `??`)
* Group capture (coming soon!) and backreferences
* Lookahead/lookbehind assertions (`(?=`, `(?!`, `(?<=`, `(?<!`)
* Conditional expressions (`(?(condition)then|else)`)
* Recursion and subroutines (`(?R)`, `(?1)`)

## Quick Start

Generate a matcher from a regex:

```sh
# Generate a Go matcher
re -p -r pcre -l go -k str 'user\d+' > user_detector.go
```

This produces a standalone matcher function.

## Supported Code Generation Targets

libfsm provides stable, “first-class” code generation for:
- High-level languages: C (via `-l vmc`), Go, Rust
- LLVM IR
- Native WebAssembly

Adding code generation for new languages is straightforward and is defined in [src/libfsm/print/](../src/libfsm/print/).

## Workflow Overview

libfsm provides two main tools for pattern matching:
- **`re`** takes patterns from the command line
- **`rx`** takes patterns from a file

A recommended workflow when using libfsm is:

1. Validate the regex

Test behavior using any PCRE-compatible tool (e.g., [pcregrep(1)](https://man7.org/linux/man-pages/man1/pcregrep.1.html) on the CLI or [https://regex101.com/](https://regex101.com/) in the browser).

2. Verify libfsm compatibility

If unsupported constructs exist, libfsm reports the failing location:
```sh
re -r pcre -l ast 'x*?'
# Output: /x*?/:3: Unsupported operator
```
In this example, `:3` indicates that the character at byte offset three in the pattern is an unsupported feature.

```sh
# patterns with unsupported operators are output to declined.txt
rx -r pcre -l ast -d declined.txt 'x*?'
```


3. Generate code

```sh
re -p -r pcre -l rust -k str '^item-[A-Z]{3}\z' > item_detector.rs
```

4. Use multiple patterns

Execution complexity for the generated code is proportional to the length of the text being matched, not to the number of patterns.
Assuming your generated code isn't too large to compile, this means you can have as many patterns as you want,
for the same time it takes to execute a single pattern.

Take advantage of this.

```sh
# re - patterns from command line:
re -p -r pcre -l go -k str '^x?a b+c$' '^x*def?$' '^x$'

# rx - patterns from file:
rx -p -r pcre -l vmc -k str -d skipped.txt patterns.txt > detectors.c
```

5. Call the generated code from your program somehow

You're on your own for this. `-k` controls the API for the generated code to read in data to match. Try different options for the language you're using and see which suits you.

The generated API can also vary depending on how you want libfsm to handle ambiguities between different patterns. See the `AMBIG_*` flags in [include/fsm/options.h](../include/fsm/options.h) for different approaches there.

Both tools:
* Combine all patterns into one function (like using `|` to join them)
* Generate code that can return `(bool, int)` for the match status and pattern ID
* Pattern ID is argument position for `re`, line number for `rx`
* When encountering unsupported patterns: `rx` can decline them to `-d` file and generates code with working patterns only; `re` fails completely

### Common Flags

| Flag | Purpose | Common Options | Notes |
|:----:|:---------------------------- |:------------------------------------------ |:---------------------------------------------------------------- |
| `-r` | Regex dialect | `pcre`, `literal`, `glob`, `native`, `sql` | `pcre` supports the widest set of features |
| `-l` | Output language for printing | `go`, `rust`, `vmc`, `llvm`, `wasm`, `dot` | Use `vmc` for `C` code. Pipe `dot` into `idot` for visualization |
| `-k` | Generated function I/O API | `str`, `getc`, `pair` | `str` takes string, `pair` takes byte array, `getc` uses callback for streaming |
| `-p` | Print mode | *(no value)* | Abbrv. of `-l fsm`. Print the constructed fsm, rather than executing it. |
| `-d` | Declined patterns | filename | Only applies to `rx` (batch mode) |

This is not an exhaustive list. For full flag details, see [include/fsm/options.h](../include/fsm/options.h) and the [man pages](../man).
The man pages can be built by running `bmake -r doc`, then view with `man build/man/re.1/re.1`.

## Writing Effective libfsm Patterns

Generally, to keep generated code compact, stick to the least expressive subset of features.

libfsm has no way to know in advance what text you'll be passing to its generated code.
For example, are you matching a string that you know will never contain a newline?
libfsm doesn't know that.
It has to generate code that's capable of handling any input.
You can help it out by making your patterns precise.

Think about what you intend your pattern to match, and what it's actually capable of matching given arbitrary text.
This helps restrict the scope of your pattern from arbitrary text to exactly what you mean.
The following bits of advice illustrate various specific ways to bring down this scope.

1. Replace broad wildcards

Avoid `.*` and `.+` when possible. Wildcards match “anything,” which is often imprecise. And although they look compact, libfsm must enumerate every possible byte and continuation. This quickly leads to large DFAs.

For example, a double-quoted string should not use `".*"` because the content cannot contain an unescaped quote. Using `.*` forces libfsm to consider all characters -- including both the presence and absence of the closing `"` at every step. This greatly increases the number of states.

Instead, restrict it to the actual valid characters `"[^"\r\n]*"`, which matches only what is allowed and will keep the DFA more compact.

Use negated character classes to match only the allowed content:

| Avoid | Better |
| ---------- | -------------- |
| `<.*>` | `<[^>]*>` |
| `\((.*)\)` | `\([^)]*\)`|
| `price=.+` | `price=[0-9]+` |
| `var\s.+=` | `var\s[^=]+=` |

The overlap between `.*` or `.+` and strings that follow is often the cause of an “explosion” in the size of the generated FSM. So when compilation is slow or generated output is large, look for `.*` and `.+` first and replace them with a narrower character class.

2. Take care with bounded repetition

If you have the pattern `^x{3,5}$`, libfsm's resulting DFA will be structured like "match an x, then match an x, then match an x, then match an x or skip it, then match an x or skip it, then report an overall match if at the end of input". It has to repeat the pattern, noting each time whether it's required or optional (beyond the lower count in `{min,max}`), because DFA execution doesn't have a counter, just the current state within the overall DFA.

When the subexpression (represented by `x`) unintentionally matches too many things, they all have to be spelled out every time.
So pay especially close attention to tightening up subexpressions in bounded repetition clauses.

3. Anchor when matching full string

When the intention is to match an entire string, use anchors.
Use `^` at the beginning and `\z` for the true end of the string.

```regex
# Correct: matches only this exact hostname
# Matches "web12.example.com"
# Does not match "foo-web12.example.com-bar"
^web\d+\.example\.com\z 

# Incorrect: would match inside a larger string
# Matches "web12.example.com"
# Also matches "foo-web12.example.com-bar"
web\d+\.example\.com
```

4. Prefer `\z` over `$` for End-of-String

`\z` always matches the end of the string.
`$` will also match a trailing newline at the end of the string,
so if you use this in combination with capturing groups, you may not be capturing what you expect.
Also, `\z` produces a smaller FSM, so it is better to use it in places where `\n` cannot appear.

```regex
# Preferred: matches only if the string ends with "bar"
# Matches "/foo/bar"
# Does NOT match "/foo/bar\n"
/bar\z

# Incorrect: allows a trailing newline,
# which is usually unintended and adds unnecessary complexity
# Matches "/foo/bar"
# Also matches "/foo/bar\n"
/bar$
```

5. Escape special characters when used as literals

Many characters have special meaning in regex (for example `.`, `+`, `*`, `?`, `[`, `(`).
If you mean to match them literally, escape them:

| Literal You Want | Correct Regex | Explanation |
|----------------------------|-----------------------------|--------------------------------------------|
| `example.com` | `example\.com` | `.` matches any character unless escaped |
| `a+b` | `a\+b` | `+` means “one or more” |
| `price?` | `price\?` | `?` means “optional” |
| `[value]` | `\[value\]` | `[` and `]` start/end a character class |
| `(test)` | `\(test\)` | `(` and `)` begin/end a group |
| Markdown link `[t](u)` | `(\[[^]]*\]\([^)]*\))` | Matches `[text](url)` without crossing `]` or `)` |

The `.` wildcard in particular is often mistakenly left unescaped in practice.
On testing, it will match a literal `.` as intended. But it will also match any other character.
This means that not only is your pattern incorrect (write negative test cases!),
but also this part of your FSM is 256 times larger than it should be.

6. Use non-capturing groups

Capture groups are _currently_ not supported (coming soon!).

If you don't need to capture things, don't use capture.
If you need grouping for alternation or precedence, use PCRE's non-capturing syntax `(?:...)`:

```regex
# Correct
(?:private|no-store)

# Not what's intended
(private|no-store)
```

## Byte Search Optimization

Patterns that start with an uncommon character can be accelerated using an initial byte scan before running the FSM.
This quickly jumps to likely match positions instead of scanning every byte.

Good candidates are patterns that start with uncommon prefix characters, for example:

```regex
#tag-[a-z]+
@user-[0-9]+
\[section\]
{"key":
"name='[^']+'"
```

These prefixes (`#`, `@`, `[`, `{`, `'`, `"`) are rare in normal text, so a byte search can skip ahead before running the matcher.

We found using `strings.IndexByte` before calling the generated matcher in Go code significantly improved performance when matching strings with a large (>5k) leading prefix.

## Pattern Matches Empty String Unintentionally

Pattern:

```regex
\s*
```

Will compile to code that always returns true.

This is only an issue if that is not what you intend.

**Fix options:**

* Require at least one match: `\s+`
* Anchor context: `^\s+$` or alternatively, use `-Fb` flag
3 changes: 3 additions & 0 deletions examples/bm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ pcre: pcre.c
libfsm: libfsm.c
gcc -o libfsm -O3 -Wall -std=c99 ${BM_CFLAGS} libfsm.c -I ../../include ../../build/lib/libre.a ../../build/lib/libfsm.a

clean:
rm -f pcre libfsm

4 changes: 2 additions & 2 deletions examples/bm/libfsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ main(int argc, char *argv[])
opt.io = FSM_IO_STR;

p = argv[0];
fsm = re_comp(RE_PCRE, fsm_sgetc, &p, &opt, flags, &e);
fsm = re_comp(RE_PCRE, fsm_sgetc, &p, NULL, flags, &e);
if (fsm == NULL) {
re_perror(RE_LITERAL, &e, NULL, s);
return 1;
Expand All @@ -80,7 +80,7 @@ main(int argc, char *argv[])
printf("#include <time.h>\n");
printf("\n");

fsm_print_c(stdout, fsm);
fsm_print(stdout, fsm, &opt, NULL, FSM_PRINT_C);

printf("int\n");
printf("main(void)\n");
Expand Down
Loading
Loading