How do I work with regular expressions?
← Getting Started · Ref: Q33
EK9 has a built-in RegEx type with /pattern/ literal syntax (same as JavaScript), avoiding double-escaping.
REGEX LITERALS
digitPattern <- /\d+/ emailPattern <- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
No string escaping needed: /\d+/ is what you mean. Java requires Pattern.compile("\\d+").
MATCHING
Bidirectional 'matches' operator:
require 'hello123' matches /[a-z]+\d+/ require /[a-z]+\d+/ matches 'hello123'
Negate with 'not matches':
require 'hello' not matches /\d+/
SPLITTING STRINGS
Split with regex delimiter, works both directions:
parts <- 'one:two:three'.split(/:/) alsoParts <- /:/.split('one:two:three')
Both produce List of String.
GROUP EXTRACTION
Capture groups extract matched portions via split():
groups <- text.split(/\{(.*?)(\d+)(.*)/)
If matched, groups is List of String per capture group. If not matched, groups is unset (see Q29).
Access: groups.getOrDefault(0, "") or stream: cat groups | skip 1 | head 1 | collect as String
ESCAPING IN LITERALS
/\\/ matches literal backslash. /\// matches literal forward slash.
Shortcuts: \d \w \s . Quantifiers: ? * + {n} {n,m}. Anchors: ^ $ \b. Classes: [a-z] [^0-9].
REGEX COMPOSITION
Build patterns with + operator:
combined <- /^Hello/ + / / + /World$/
Extend with +=:
pattern <- /^Start/ pattern += /.*End$/
COMPARISON AND IDENTITY
== and <> compare patterns. ? checks if set. $ converts to string. length returns pattern length.
Use 'ek9 -h RegEx' and 'ek9 -h String' for the full API.
See Q37 for string operations. See Q29 for tri-state and unset group extraction.
Example
defines module qa.regex defines program RegExDemo() stdout <- Stdout() // === REGEX LITERALS === // Regex literals use /pattern/ syntax - like JavaScript // No double-escaping needed (unlike Java's Pattern.compile("\\d+")) digitPattern <- /\d+/ emailPattern <- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ stdout.println(`Digit pattern: ${digitPattern}`) require "user@example.com" matches emailPattern require "not-an-email" not matches emailPattern // === MATCHING === // The 'matches' operator works in both directions require "hello123" matches /[a-z]+\d+/ require /[a-z]+\d+/ matches "hello123" // 'not matches' for negation require "hello" not matches /\d+/ // Match name variants with character classes and alternation namePattern <- /[S|s]te(?:ven?|phen)/ require "Steve" matches namePattern require "steve" matches namePattern require "Stephen" matches namePattern require "Steven" matches namePattern require "Stephene" not matches namePattern // Character class with quantifier sixChars <- /[a-zA-Z0-9]{6}/ require sixChars matches "arun32" require sixChars not matches "kkvarun32" require sixChars not matches "arun$2" // === SPLITTING STRINGS === // Split works both directions: string.split(regex) or regex.split(string) colonRegEx <- /:/ colonDelimited <- "one:two:three:four:five" fromString <- colonDelimited.split(colonRegEx) fromRegEx <- colonRegEx.split(colonDelimited) require fromString == fromRegEx stdout.println(`Split: ${fromString}`) require $fromString == "one,two,three,four,five" // === GROUP EXTRACTION === // Capture groups in the pattern extract matched portions text <- "This is a sample Text 1234 with numbers in between." groupPattern <- /\{(.*?)(\d+)(.*)/ groups <- text.split(groupPattern) stdout.println(`Groups: ${groups}`) require length groups == 3 require groups.getOrDefault(0, "") == "This is a sample Text " require groups.getOrDefault(2, "") == " with numbers in between." // Extract specific group using stream pipeline justNumber <- cat groups | skip 1 | head 1 | collect as String require $justNumber == "1234" stdout.println(`Extracted number: ${justNumber}`) // Convert extracted text to Integer asInteger <- Integer(justNumber) require asInteger == 1234 stdout.println(`As integer: ${asInteger}`) // Get last two groups lastTwo <- cat groups | tail 2 | collect as List of String require length lastTwo == 2 // === NO MATCH RETURNS UNSET === noNumbers <- "No digits here at all." noMatch <- noNumbers.split(groupPattern) require ~noMatch? stdout.println(`No match isSet: ${noMatch?}`) // === ESCAPING IN LITERALS === // Backslash in regex literal: /\\/ matches a literal backslash backslashPattern <- /^Some\\Thing$/ require backslashPattern matches "Some\Thing" // Forward slash in regex literal: /\// matches a literal forward slash slashPattern <- /^Some\/Thing$/ require slashPattern matches "Some/Thing" // Fraction matching with escaped slash fractionPattern <- /.*\/.*/ require fractionPattern matches "3/4" // === REGEX COMPOSITION === // Build patterns with + operator prefix <- /^Hello/ combined <- prefix + / / + /World$/ require combined matches "Hello World" stdout.println(`Combined: ${combined}`) // += to extend a pattern growing <- /^Start/ growing += /.*End$/ require growing matches "Start and End" // === COMMON PATTERNS === // Phone number require "555-1234" matches /^\d{3}-\d{4}$/ // UK postcode (simplified) require "SW1A 1AA" matches /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/ // ISO date format require "2024-06-15" matches /^\d{4}-\d{2}-\d{2}$/ stdout.println("All regex tests passed")
Common mistakes
E50001 — EK9 rejects variable names that shadow operator keywords. 'matches' is a reserved operator keyword. Use descriptive names like 'digitPattern' or 'emailPattern'. See ek9 -h E50001 for details.
Incorrect:
matches <- /\d+/
Correct:
digitPattern <- /\d+/
Other ways to ask this
- Does EK9 have regex support?
- How do I match patterns in strings?
- How do I split strings with a regex in EK9?
- How do I extract groups from a regex match?
Coming from another language?
Java: Pattern.compile() double-escaping, Pattern/Matcher ceremony. Python: re.compile(r'pattern'). JavaScript: /pattern/ (EK9 follows this). Rust/Go: no literal syntax. EK9: /pattern/ literal, bidirectional matches, split both directions, no double-escaping.
Keywords: match, group, escape, quantifier, expression, start, beginner, regular, class, character, split, migrate, anchor, capture, literal, pattern, first, intro, regex, matches