Advanced Features
On this page 27
This document describes the advanced features available in the zig-regex library.
Table of Contents
- Regex Macros & Composition
- Type-Safe Builder API
- C FFI
- WASM Support
- Regex Lint & Analysis
- Performance Features
Regex Macros & Composition
The macro system allows you to define reusable pattern components and compose them into larger patterns.
Basic Usage
const std = @import("std");
const macros = @import("regex").macros;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create a macro registry
var registry = macros.MacroRegistry.init(allocator);
defer registry.deinit();
// Define macros
try registry.define("digit", "[0-9]");
try registry.define("word", "[a-zA-Z]+");
try registry.define("email_local", "[a-zA-Z0-9._%+-]+");
try registry.define("email_domain", "[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
// Expand macros in patterns
const phone_pattern = try registry.expand("\\(?${digit}{3}\\)?[-. ]?${digit}{3}[-. ]?${digit}{4}");
defer allocator.free(phone_pattern);
const email_pattern = try registry.expand("${email_local}@${email_domain}");
defer allocator.free(email_pattern);
std.debug.print("Phone pattern: {s}\n", .{phone_pattern});
std.debug.print("Email pattern: {s}\n", .{email_pattern});
}
Common Predefined Macros
const macros = @import("regex").macros;
// Load all common macros
var registry = macros.MacroRegistry.init(allocator);
try macros.CommonMacros.loadInto(®istry);
// Available macros:
// - digit: [0-9]
// - word: [a-zA-Z]
// - word_char: [a-zA-Z0-9_]
// - whitespace: [ \t\r\n]
// - alpha: [a-zA-Z]
// - alnum: [a-zA-Z0-9]
// - hex: [0-9a-fA-F]
// - lower: [a-z]
// - upper: [A-Z]
// - email_local: [a-zA-Z0-9._%+-]+
// - email_domain: [a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
// - ipv4_octet: (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
// - url_scheme: https?
Type-Safe Builder API
The builder API provides a fluent interface for constructing regex patterns programmatically, reducing errors and improving readability.
Basic Example
const std = @import("std");
const Builder = @import("regex").Builder;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create a builder
var builder = Builder.init(allocator);
defer builder.deinit();
// Build a pattern: ^\d{3}-\d{3}-\d{4}$
_ = try builder
.startOfLine()
.digit().repeatExact(3)
.literal("-")
.digit().repeatExact(3)
.literal("-")
.digit().repeatExact(4)
.endOfLine();
// Compile the pattern
var regex = try builder.compile();
defer regex.deinit();
// Use it
if (try regex.isMatch("555-123-4567")) {
std.debug.print("Valid phone number!\n", .{});
}
}
Builder Methods
// Literals and character classes
.literal("text") // Escaped literal text
.any() // Match any character (.)
.digit() // Match digit (\d)
.word() // Match word character (\w)
.whitespace() // Match whitespace (\s)
.charClass("abc") // Custom character class [abc]
.notCharClass("abc") // Negated class [^abc]
.charRange('a', 'z') // Character range [a-z]
// Quantifiers
.oneOrMore() // + (greedy)
.zeroOrMore() // _ (greedy)
.optional() // ? (greedy)
.repeatExact(n) // {n}
.repeatAtLeast(n) // {n,}
.repeatRange(min, max) // {min,max}
// Anchors and boundaries
.startOfLine() // ^
.endOfLine() // $
.wordBoundary() // \b
// Groups and alternation
.startGroup() // (
.endGroup() // )
.startNonCapturingGroup() // (?:
.or_() // |
Predefined Patterns
const Patterns = @import("regex").Patterns;
// Email validation
const email_pattern = try Patterns.email(allocator);
defer allocator.free(email_pattern);
// Available patterns:
// - email(allocator)
// - url(allocator)
// - ipv4(allocator)
// - phoneUS(allocator)
// - dateISO(allocator) // YYYY-MM-DD
// - time24(allocator) // HH:MM or HH:MM:SS
// - hexColor(allocator) // #RGB or #RRGGBB
// - creditCard(allocator)
// - uuid(allocator)
// - integer(allocator)
// - decimal(allocator)
// - identifier(allocator) // Variable names
Pattern Composition
const Composer = @import("regex").Composer;
var composer = Composer.init(allocator);
defer composer.deinit();
// Add patterns
_ = try composer.add("foo");
_ = try composer.add("bar");
_ = try composer.add("baz");
// Compose with alternation: foo|bar|baz
const alt_pattern = try composer.alternatives();
defer allocator.free(alt_pattern);
// Compose with concatenation: foobarbaz
const seq_pattern = try composer.sequence();
defer allocator.free(seq_pattern);
// Wrap in a group: (foo|bar|baz)
const grouped = try composer.group();
defer allocator.free(grouped);
C FFI
The C API provides a stable ABI for using zig-regex from other languages.
C Header (c_api.h)
typedef struct ZigRegex ZigRegex;
typedef struct ZigMatch ZigMatch;
typedef struct ZigMatchArray ZigMatchArray;
typedef struct {
bool case_insensitive;
bool multiline;
bool dot_all;
bool unicode;
} ZigRegexFlags;
typedef struct {
size_t start;
size_t end;
size_t capture_count;
} ZigMatchInfo;
typedef enum {
ZIG_REGEX_SUCCESS = 0,
ZIG_REGEX_OUT_OF_MEMORY = 1,
ZIG_REGEX_INVALID_PATTERN = 2,
ZIG_REGEX_COMPILATION_FAILED = 3,
ZIG_REGEX_MATCH_FAILED = 4,
ZIG_REGEX_INVALID_HANDLE = 5,
ZIG_REGEX_BUFFER_TOO_SMALL = 6,
} ZigRegexError;
// Compile a regex
ZigRegex_ zig_regex_compile(const char_ pattern, ZigRegexFlags flags, ZigRegexError_ error_code);
// Free a regex
void zig_regex_free(ZigRegex_ regex);
// Test if pattern matches
bool zig_regex_is_match(ZigRegex_ regex, const char_ input, ZigRegexError_ error_code);
// Find first match
ZigMatch_ zig_regex_find(ZigRegex_ regex, const char_ input, ZigRegexError_ error_code);
// Get match info
ZigRegexError zig_match_get_info(ZigMatch_ match, ZigMatchInfo_ info);
// Get matched text
size_t zig_match_get_text(ZigMatch_ match, char_ buffer, size_t buffer_len);
// Get capture group
size_t zig_match_get_capture(ZigMatch_ match, size_t capture_index, char_ buffer, size_t buffer_len);
// Free match
void zig_match_free(ZigMatch_ match);
// Find all matches
ZigMatchArray_ zig_regex_find_all(ZigRegex_ regex, const char_ input, ZigRegexError_ error_code);
// Get array length
size_t zig_match_array_len(ZigMatchArray_ matches);
// Get match from array
ZigMatch_ zig_match_array_get(ZigMatchArray_ matches, size_t index);
// Free match array
void zig_match_array_free(ZigMatchArray_ matches);
// Replace first match
size_t zig_regex_replace(ZigRegex_ regex, const char_ input, const char_ replacement,
char_ buffer, size_t buffer_len, ZigRegexError_ error_code);
// Get library version
const char_ zig_regex_version();
C Usage Example
int main() {
ZigRegexFlags flags = {
.case_insensitive = false,
.multiline = false,
.dot_all = false,
.unicode = false,
};
ZigRegexError error;
// Compile regex
ZigRegex_ regex = zig_regex_compile("\\d{3}-\\d{3}-\\d{4}", flags, &error);
if (!regex) {
fprintf(stderr, "Failed to compile regex: %d\n", error);
return 1;
}
// Test match
const char_ input = "Call me at 555-123-4567";
ZigMatch_ match = zig_regex_find(regex, input, &error);
if (match) {
// Get match info
ZigMatchInfo info;
zig_match_get_info(match, &info);
// Get matched text
char buffer[256];
size_t len = zig_match_get_text(match, buffer, sizeof(buffer));
printf("Found match: %s (at position %zu-%zu)\n", buffer, info.start, info.end);
zig_match_free(match);
} else {
printf("No match found\n");
}
zig_regex_free(regex);
return 0;
}
Python FFI Example
import ctypes
import os
# Load the shared library
lib = ctypes.CDLL("./zig-out/lib/libzig-regex.so")
# Define structures
class ZigRegexFlags(ctypes.Structure):
_fields_ = [
("case_insensitive", ctypes.c_bool),
("multiline", ctypes.c_bool),
("dot_all", ctypes.c_bool),
("unicode", ctypes.c_bool),
]
# Define function signatures
lib.zig_regex_compile.argtypes = [ctypes.c_char_p, ZigRegexFlags, ctypes.POINTER(ctypes.c_int)]
lib.zig_regex_compile.restype = ctypes.c_void_p
lib.zig_regex_is_match.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int)]
lib.zig_regex_is_match.restype = ctypes.c_bool
lib.zig_regex_free.argtypes = [ctypes.c_void_p]
lib.zig_regex_free.restype = None
# Use it
flags = ZigRegexFlags(case_insensitive=False, multiline=False, dot_all=False, unicode=False)
error = ctypes.c_int()
regex = lib.zig_regex_compile(b"\\d+", flags, ctypes.byref(error))
if regex:
is_match = lib.zig_regex_is_match(regex, b"hello123", ctypes.byref(error))
print(f"Match: {is_match}")
lib.zig_regex_free(regex)
WASM Support
The library can be compiled to WebAssembly for use in browsers and Node.js.
Building for WASM
# Build for WASM
zig build-lib src/c_api.zig -target wasm32-freestanding -dynamic -rdynamic
# Or use the provided build script
zig build wasm
JavaScript Usage
// Load WASM module
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('zig-regex.wasm')
);
const {
zig_regex_compile,
zig_regex_is_match,
zig_regex_free,
memory
} = wasmModule.instance.exports;
// Helper to convert JS string to WASM memory
function stringToWasm(str) {
const bytes = new TextEncoder().encode(str + '\0');
const ptr = allocate(bytes.length);
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
return ptr;
}
// Compile regex
const pattern = stringToWasm('\\d+');
const flags = 0; // No flags
const errorPtr = allocate(4);
const regex = zig_regex_compile(pattern, flags, errorPtr);
// Test match
const input = stringToWasm('hello123');
const isMatch = zig_regex_is_match(regex, input, errorPtr);
console.log('Match:', isMatch);
// Clean up
zig_regex_free(regex);
Regex Lint & Analysis
The linter analyzes regex patterns and provides warnings about potential issues.
Basic Usage
const std = @import("std");
const Linter = @import("regex").Linter;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var linter = Linter.init(allocator);
defer linter.deinit();
// Lint a pattern
try linter.lint("(a+)+");
// Print report
const stdout = std.io.getStdOut().writer();
try linter.printReport(stdout);
}
Detectable Issues
The linter can detect:
- Nested Quantifiers: Patterns like
(a+)+that can cause catastrophic backtracking - Greedy Wildcards:
.*and.+that can be slow - Empty Alternation Branches:
a||b - Unused Capture Groups: Groups that are never referenced
- Redundant Quantifiers:
a??ora** - Anchors in Quantifiers:
(^)+which won't work as expected - Very Large Repetitions:
a{10000}that could cause issues - Backtracking Risks: Complex patterns prone to exponential behavior
Example Output
Warnings (2):
[WARNING] at position 0: Nested quantifiers detected - can cause catastrophic backtracking
Suggestion: Consider simplifying the pattern or using atomic groups
[WARNING] at position 0: Greedy wildcard quantifier (._ or .+) can be slow
Suggestion: Consider using lazy quantifier (._? or .+?) or being more specific
Performance Features
Hybrid Engine Architecture
The library automatically chooses between two engines:
- Thompson NFA: O(n*m) performance for basic patterns
- Backtracking Engine: Supports advanced features (lazy quantifiers, lookahead, lookbehind, backreferences)
// This uses Thompson NFA (fast)
var regex1 = try Regex.compile(allocator, "\\d+[a-z]+");
// This uses backtracking (supports lazy quantifiers)
var regex2 = try Regex.compile(allocator, "a+?b");
// This uses backtracking (supports lookahead)
var regex3 = try Regex.compile(allocator, "foo(?=bar)");
Optimization Features
- Literal Prefix Optimization: Patterns starting with literals use
indexOffor fast scanning - Anchored Optimization: Patterns anchored at start/end avoid unnecessary scanning
- Min/Max Length Calculation: Quickly reject inputs that are too short/long
Benchmarking
const benchmark = @import("regex").benchmark;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
try benchmark.runAllBenchmarks(allocator);
}
Thread Safety
The library provides thread-safe regex matching:
const thread_safety = @import("regex").thread_safety;
var regex = try Regex.compile(allocator, "\\d+");
defer regex.deinit();
// Thread-safe wrapper
var safe_regex = thread_safety.ThreadSafeRegex.init(®ex);
// Can be used from multiple threads
const result = try safe_regex.isMatch("123");
Profiling
Built-in profiling support:
const profiling = @import("regex").profiling;
var regex = try Regex.compile(allocator, "a+b+c+");
defer regex.deinit();
// Enable profiling
profiling.enable();
// Run matches
_ = try regex.isMatch("aaabbbccc");
// Get statistics
const stats = profiling.getStats();
std.debug.print("Matches: {d}, Total time: {d}ns\n", .{stats.match_count, stats.total_time_ns});
Summary
The zig-regex library provides:
- ✅ Regex macros for pattern composition
- ✅ Type-safe builder API
- ✅ C FFI for language interoperability
- ✅ WASM support for web environments
- ✅ Lint and analysis tools
- ✅ Hybrid engine (Thompson NFA + Backtracking)
- ✅ Performance optimizations
- ✅ Thread safety
- ✅ Profiling support
All features are production-ready and fully tested with 175+ test cases passing.