Skip to content

Custom recognizers

Add your own detector by implementing analyzer.Recognizer (or reuse analyzer.PatternRecognizer) and registering it:

reg := analyzer.NewRegistry("en")
recognizers.LoadDefaults(reg, "en")          // built-ins (optional)
reg.Add("en", analyzer.NewPatternRecognizer(
    "InternalIDRecognizer", "INTERNAL_ID", "en",
    []*analyzer.Pattern{analyzer.MustPattern("internal-id", `\bEMP-\d{6}\b`, 0.9)},
).WithValidator(myChecksum))

eng := analyzer.NewEngine(reg, []string{"en"})

WithContext("employee", "emp id", "staff") declares the words that support the match; see context-aware scoring.

The Recognizer interface is the seam for statistical backends too; nothing in the framework assumes regex. The alcatraz/ner module plugs in through the same interface.

Emulating lookaround without a backtracking engine

Go's RE2 regexp omits lookaround and backreferences by design, and that omission is what guarantees linear-time matching. Two pure-Go tools cover most lookaround needs; the third option is the lookaround module.

A. Context-aware validator. For "match X only when surrounded by Y". The validator sees the full text and the match's byte span:

rec := analyzer.NewPatternRecognizer("PinRule", "PIN", "en",
    []*analyzer.Pattern{analyzer.MustPattern("pin", `\d{4}`, 0.5)},
).WithContextValidator(func(text string, start, end int) bool {
    return strings.HasSuffix(text[:start], "PIN ") // emulates (?<=PIN )
})

It is a filter (keep/drop) and never inflates the score the way a checksum WithValidator does; the two compose if you need both.

B. Capture-group span. Match the surrounding context but report only the captured entity. WithGroup(n) selects which group becomes the result span:

// Emulates (?<=user=)\w+ : require the prefix, emit only the value.
p := analyzer.MustPattern("user", `user=(\w+)`, 0.9).WithGroup(1)

None of the 45 built-ins need more than A + B: they rely on \b anchors plus validators and same-entity dedup.