String and symbol patterns are first-class type values. They validate schemas,
select match arms, check values with is, and drive string find, replace
and split (S11.1.2v4, S17.6.1).
Related: Types, String Functions, Validation.
Both domains use the same balanced delimiter. Quotes and named patterns establish one consistent domain per island:
type StringIdent = \("\a" "\w"*)
type SymbolIdent = \('\a' '\w'*)
"abc1" is StringIdent // true
'abc1' is SymbolIdent // true
"abc1" is SymbolIdent // false
fn width(s: \("\a"+)) => len(s)
let code: \("\a"{3}) = "abc"
The retired \symbol(...) spelling is rejected. The delimiter distinguishes
repetition within one text value from an ordinary type occurrence, which counts
separate values (S11.1.6v3):
type Values = "a"* // null, "a", or a run of separate "a" values
type Text = \("a"*) // "", "a", "aa", ...
Ordinary quoted characters are literal. A literal-only island is the same type
as its same-domain literal union: \("hello" | "hi") equals "hello" | "hi";
\('if' | 'else') equals 'if' | 'else'.
Class escapes are recognized only inside an island:
| String atom | Symbol atom | Matches |
|---|---|---|
"\d" |
'\d' |
digit [0-9] |
"\w" |
'\w' |
word character [a-zA-Z0-9_] |
"\s" |
'\s' |
whitespace [\t\n\f\r ] |
"\a" |
'\a' |
alphabetic [a-zA-Z] |
Bare d, w, s, a are ordinary named-pattern references. Quoted fragments
can mix literal characters and classes. Only the four designated escapes
introduce classes; operators stay outside quotes. Ordinary escapes are decoded
once. Outside an island, the four new escapes are invalid.
type Code = \("ID-\d" "\w"*)
type SymbolCode = \('ID-\d' '\w'*)
"1+" is \("\d+") // true: digit then literal plus
"12" is \("\d"+) // true: repeated digit
"\\d" is \("\\d") // true: literal backslash and d
"\n" is \("\n") // true: literal newline
A range is one atom; its bounds must be single Unicode code points:
type Lower = \("a" to "z")
type SymbolLower = \('a' to 'z')
type HexDigit = \("0" to "9" | "a" to "f" | "A" to "F")
type Greek = \("α" to "ω")
Ordinary string range types may also be referenced in string patterns (S11.1.3).
| Quantifier | Meaning within an island |
|---|---|
? |
zero or one |
+ |
one or more |
* |
zero or more |
{n} |
exactly n |
{n+} |
at least n |
{n,m} |
between n and m, inclusive |
A quantifier repeats the entire preceding atom. One quoted fragment is one atom, including its embedded classes:
"abab" is \("ab"+) // true
"a a " is \("a\s"*) // true
type Phone = \("\d"{3} "-" "\d"{3} "-" "\d"{4})
type Zip = \("\d"{5} ("-" "\d"{4})?)
Counts use braces; {n,} is rejected. The matching engine caps counts at 1000.
Binding order, tightest first: atom (including a range), prefix !, quantifier,
concatenation, | (S11.1.2v4). Group with parentheses.
"bc" is \("a" | "b" "c") // true: a or bc
"ac" is \(("a" | "b") "c") // true: grouping changes the order
"xy" is \(!"\d"+) // true: (!"\d")+
Every quoted fragment and named reference must have the same domain, even across
alternatives. \("a" 'b') and \("a" | 'b') are compile errors. A mixed-domain
union is written between whole islands: \("\d"+) | \('\d'+).
The island's only binary operator is |. Intersect or exclude whole patterns
with the type operators (S10.1.1v3):
type AlphaWord = \("\a"+) & \("\w"+)
type NonNumericWord = \("\w"+) ! \("\d"+)
Prefix ! complements a single-character set in the island's domain: a class,
range, one-character literal, or a union, group, negation or named pattern built
from these. Negating a multi-character fragment or repetition is a compile error.
type NotDigit = \(!"\d")
type SymbolNotDigit = \(!'\d')
type NotABC = \(!("a" | "b" | "c"))
type Tag = \("<" (!">")* ">")
Whole-type complement is written outside the island: !\("\d"+).
Bare . matches one code point except newline; ... matches any sequence,
including newlines. Both inherit the domain established elsewhere in the island:
type StringTail = \("prefix" ...)
type SymbolTail = \('prefix' ...)
type AnyString = \("" ...)
type StringPair = \("" . .)
An island with no domain-bearing literal or reference is a compile error,
including \(.), \(...) and \((.)*). There is no default string domain or
implicit dual-domain pattern. Quoted "." and "..." are literal punctuation.
type Email = \("\w"+ "@" "\w"+ "." "\a"{2,6})
type Version = \("v" "\d"+ "." "\d"+ "." "\d"+)
type DatePattern = \("\d"{4} "-" "\d"{2} "-" "\d"{2})
type Username = \("\a" "\w"{2,19})
Single-quoted fragments and class escapes provide the same operations in the symbol domain. Named references keep their domain:
type SymbolDigits = \('\d'+)
type SymbolCode = \('ID-' SymbolDigits)
type Copy = \(SymbolDigits)
Implicit cross-domain reuse and the old \symbol(StringPattern) lift are
rejected. String and symbol values never satisfy each other's patterns.
type Digits = \("\d"+)
fn label(value: Digits) => "number:" ++ value
let code: Digits = "123"
"123" is Digits // true
is and match require the whole text to match; match chooses the first
matching arm (S11.2.1):
fn classify(value) => match value {
case \("\d"+): "string digits"
case \('\d'+): "symbol digits"
default: "other"
}
find, replace and split search within a string and require string-domain
patterns; symbol patterns produce a domain error.
type Digits = \("\d"+)
find("a1b22", Digits) // [{value: "1", index: 1}, {value: "22", index: 3}]
replace("a1b22", Digits, "N") // "aNbN"
split("a1b22c", Digits) // ["a", "b", "c"]
split("a1b22c", Digits, true) // ["a", "1", "b", "22", "c"]
Replacement text is literal; empty-matching patterns yield empty matches too (S17.6.1). Search indices count Unicode code points (S17.4.1).