16. Predicates and pattern matching
Predicates classify, test or match a value. They produce Boolean results and support ordinary negation. Successful type/pattern tests can establish narrower types or scoped captures.
16.1 Absence, emptiness, whitespace and type
CS-PREDICATE-001. is nothing tests actual absence only; is not nothing negates it. is empty includes absence, a meaningful effective type default under applicable equality, or a collection/sequence with no elements. Zero, false, Guid.Empty, DateTime.MinValue and an empty String are empty. A whitespace-only nonempty String is not empty; whitespace is a separate String predicate.
For a sequence, emptiness is an Any-style probe, with an equivalent count/shape fast path permitted when it preserves observable behavior. For an ordinary reference type without meaningful default/value equality, only nothing is empty. No structural equality is invented merely to implement this test. Is default is not a predicate; use ordinary comparison with contextual default when that specific comparison is wanted.
CS-PREDICATE-002. String whitespace follows IsNullOrWhiteSpace-style behavior. is in tests membership in a compatible collection/range or String containment where applicable. String containment is case-insensitive by default; in_case_sensitive selects its String-only case-sensitive form. Collection membership retains its selected comparer contract rather than acquiring a String-only interpretation.
CS-PREDICATE-003. is a T tests runtime assignability and flow-narrows the proven subject on its successful path, including contextual continuation when identity is preserved safely. It does not invoke value conversion. is exactly T tests exact runtime type, rather than assignability. is same_as tests reference identity, not value equality. Corresponding is not forms negate the predicate.
if customer is a PreferredCustomer and .IsActive then
customer.ApplyBenefits()
end if
PreferredCustomer and ApplyBenefits are ordinary application symbols. The type proof permits their members in the true branch. A getter returning a different object on each read is not an excuse to treat distinct receiver evaluations as one stable narrowed value.
16.2 String predicates and ordinary Boolean members
CS-PREDICATE-004. String relationship predicates are containing, starting_with and ending_with, with their corresponding _case_sensitive variants. Default comparisons are deterministic and culture-stable. Classification forms include ascii, numeric, integer, decimal, iso_date and parsable_as Type; they do not return parsed values. Iso_date is the narrow yyyy-MM-dd calendar-date form. Parsable_as requires a recognized parsing contract; an unsupported target is a compilation error, not arbitrary conversion discovery.
CS-PREDICATE-007. Numeric String classifiers use invariant ASCII forms after removing surrounding whitespace according to ordinary CLR String.Trim semantics. Absence, empty text and whitespace-only text classify false. A leading ASCII plus/minus sign is permitted. Interior whitespace, grouping separators, currency signs, base prefixes, underscores, Unicode digits and NaN/infinity names are not numeric classifier forms.
The accepted shapes are:
Digits := [0-9]+
Sign := "+" | "-"
IntegerText := [Sign] Digits
DecimalText := [Sign] Digits ["." Digits]
NumericText := DecimalText [("e" | "E") [Sign] Digits]
Integer-shaped text is also a decimal/numeric shape. Decimal excludes scientific exponents; numeric permits them. A present decimal point requires digits on both sides. These rules constrain classifier inputs; they do not change source literal syntax, imported Parse APIs or target-specific parsable_as contracts.
CS-PREDICATE-008. Accepted lexical shape is necessary but not sufficient for numeric String classification. is integer additionally requires representability as Long; is decimal requires representability as Decimal; is numeric requires a finite Double result. Apply these checks using the invariant input grammar above, not an arbitrary imported parser's default styles. Out-of-range/unrepresentable values classify false rather than throwing. A Double parse yielding infinity is false. Negated predicates negate that Boolean result.
let ordinary = "42" is integer
let tooLarge = "999999999999999999999999999999" is integer
let infinite = "1e1000" is numeric
// ordinary is true; tooLarge and infinite are false
These fixed classifier ranges do not choose a destination for conversion. Range gating uses normal native numeric parsing precision/rounding; it does not require an exact text-to-value round trip. A Double underflow that parses to finite zero passes the numeric range gate. Use parsable_as with a specific target when that target's recognized parsing contract is the question; imported Parse/TryParse calls retain their own styles, culture and failure behavior. A text may satisfy one numeric classifier while failing another classifier's range or grammar.
| Text | Integer shape | Decimal shape | Numeric shape |
|---|---|---|---|
" +12 " |
Yes | Yes | Yes |
"-12.50" |
No | Yes | Yes |
"1e3" |
No | No | Yes |
".5", "5." |
No | No | No |
"1,000", "1_000", "NaN" |
No | No | No |
nothing, "", " " |
No | No | No |
if fileName is ending_with ".csv" and
fileName is starting_with "report-" then
ImportReport(fileName)
end if
CS-PREDICATE-005. Boolean Is*/Has* properties and actions can expose conversational predicates using a real word boundary and the original declared spelling. IsValidEmail becomes is valid_email; HasDebt becomes has debt. Only Boolean members qualify. PascalCase, camelCase and snake_case prefixes are recognized; unrelated names such as Island or Hash do not qualify.
CS-PREDICATE-006. IsNot*/HasNot* members expose their own negative results directly. IsNotClosed maps to is not closed, not a generated call to not IsClosed. Their logical consistency is the type author's responsibility. Built-in predicate meanings take precedence over derived aliases; a colliding ordinary member remains directly callable. No can prefix, generic predicate declaration or built-in domain validator is implied.
16.3 Wildcard and sequence patterns
CS-PATTERN-001. Is like explicitly performs wildcard matching; equality does not interpret wildcard syntax. Star matches zero or more items/characters; question mark matches exactly one. String matching is case-insensitive unless like_case_sensitive is selected. Regex is a separate facility.
CS-PATTERN-002. Sequence patterns require suitable ordered/indexable/countable targets. Arbitrary one-shot IEnumerable sources must not be enumerated invisibly to manufacture indexed pattern access.
CS-PATTERN-003. A sequence level permits at most one variable-length star section, captured or ignored. Nested levels apply the limit independently. Fixed-position relative comparisons and supported nested sequence patterns are permitted.
CS-PATTERN-004. let name = pattern introduces a capture only in the successful matching scope. A question-mark capture selects one element; a star capture selects its matched sequence with the natural representable target element/sequence type. Captures cannot leak into paths where matching failed.
if amounts is like [> 0, let middle = *, < 1000] then
ProcessAmounts(middle)
end if
For an array target, the corresponding C# list-pattern shape is:
if (amounts is [> 0, .. var middle, < 1000])
ProcessAmounts(middle);
The capture is not created on failure. A different supported sequence type must preserve its applicable indexing/capture contract; the illustration does not make every enumerable an array.
String captures use double-curly let embedding:
if fileName is like "report-{{let name = *}}.csv" then
Print(name)
end if
The name local has String type. In this context the let embedding is a capture, not ordinary interpolation.
CS-PATTERN-005. Wildcard escapes backslash-star, backslash-question and doubled backslash select literal star, question mark and backslash. These are matcher escapes; backslash is not a general String lexer escape. SQL percent/underscore wildcard meanings and regex character classes are not imported into like.
16.4 Regex captures
CS-REGEX-001. Is match uses .NET regex syntax. Ordinary matching is case-insensitive; match_case_sensitive selects case-sensitive matching. Wildcard capture embedding is not regex syntax.
CS-REGEX-002. Automatic named locals require a compile-time-known pattern. Their scope is the successful branch. A dynamic pattern introduces no compiler-invented names; ordinary .NET Match/Group APIs remain available.
CS-REGEX-003. A non-repeat-capable named capture has String type. A nonparticipating group supplies nothing; a participating empty group supplies an empty String.
CS-REGEX-004. A repeat-capable named capture has List <of String> type, containing every .NET capture in order. Zero captures after a successful match give an empty list, not nothing. Repeatability is structural and established at compile time; duplicate names in mutually exclusive branches alone do not make a group repeat-capable.
if text is match "^(?<id>[0-9]+)(,(?<id>[0-9]+))*$" then
for_each value in id
ProcessIdentifier(value)
end for_each
end if
For 10,20,30, id contains the three strings in order. The local is a list throughout this successful branch; it does not change its static type according to a particular match's capture count.
CS-REGEX-005. Only names satisfying the ordinary ASCII identifier grammar are promoted. A regex-engine-valid name outside that grammar does not invalidate the regex; it introduces no automatic local.
CS-REGEX-006. A promoted keyword name uses ordinary @ escaping. Promotable names differing only by case are a compile-time ambiguity; the compiler must not silently select or rename one. Unnamed groups do not introduce locals.
if text is match "^(?<class>[A-Z]+)$" then
Print(@class)
end if
16.5 Reference lowering and diagnostics
After a successful .NET Match, capture projections have different absence contracts:
string? optional = match.Groups["optional"].Success
? match.Groups["optional"].Value
: null;
List<string> repeated = match.Groups["id"].Captures
.Cast<Capture>()
.Select(capture => capture.Value)
.ToList();
The code assumes ordinary collections/LINQ/regex imports. Regex option, timeout and caching policies remain library concerns; this projection does not choose them. A convention predicate lowers to its resolved ordinary Boolean member, and reference identity to the corresponding CLR identity test.
Reject a predicate inappropriate for its receiver, a parsing target without a recognized contract, more than one variable-length sequence wildcard at one level, an unsupported sequence target, capture use outside successful scope and case-insensitive promoted-name collisions. Keyword captures require identifier escaping. Matcher-specific backslash handling must not change how the source lexer handles unrelated Strings.
Specification contents · Previous: 15. Collections, enumeration and consumption · Next: 17. Async, iteration and concurrency