# CQLi / CQL Language Reference (condensed for language models) > CQL (Chess Query Language) is a domain-specific language for searching chess > games. CQLi is the implementation of CQL described here. This file is a > complete, self-contained condensation of the CQLi Reference Manual, written > for an LLM that has been asked to produce a working CQL query. Source of > truth: the CQLi Reference Manual at https://cql64.com/manual/ > (CQLi 1.0.8). Where this file and the manual disagree, the manual wins. ## HOW TO USE THIS FILE You are probably reading this because someone asked you to write a CQL query. 1. **Use only the filters listed in section 26 (Filter Index).** CQL is a small language with an unusual design. If a construct you want is not in this file, it does not exist. Do not invent filters, parameters, or syntax by analogy with SQL, regex, Python, or with CQL examples you may have memorized. Most wrong CQL queries fail because a plausible-sounding filter name was invented. 2. **Read section 4 (types and matching) before writing anything.** The single most common source of silently wrong queries is the difference between a filter *yielding a value* and a filter *matching the position*. `0` and `""` match. `false`, `None`, and `[]` do not. 3. **Check section 30 (Common Mistakes) before you answer.** It lists the traps that produce queries that parse but do the wrong thing. 4. **Prefer the simple form.** A CQL query is a list of filters; a position matches when all of them match. Most useful queries are three to six lines with no variables, no functions, and no loops. Reach for `piece`/`square` iteration, `find`, `echo`, and functions only when the plain form cannot express the question. 5. **Test the query if you can.** If you have `cqli` available, run `cqli --cql '' --parse` to check that it compiles, then run it against a real PGN file. If you cannot test it, say so. 6. **Say what the query does.** Explain the query in one or two sentences, and flag any place where you had to guess at the user's intent (for example, whether they want both colors, whether variations should be searched, whether "in a row" means consecutive plies or consecutive moves by one side). Notation used in this file: `[x]` means optional, `{a|b}` means choose one, *italic-style names* are placeholders, `...` means "one or more of the preceding". Types are written *Set*, *Numeric*, *String*, *Boolean*, *Position*, *Piece*, *Dictionary*. --- ## 1. EXECUTION MODEL This is the mental model that everything else depends on. - CQLi reads a PGN file and a CQL query, and writes out the games that match. - A CQL query is **one or more filters**. Whitespace and newlines between filters are not significant. - For each game, CQLi builds a **game tree**. Every position gets a sequential **position ID**, starting at 0 for the initial position. - Each position is visited once, in increasing position-ID order. **The whole query is evaluated at every position.** - At a position, the filters are evaluated in order. **As soon as one filter fails to match, evaluation stops for that position** and CQLi moves to the next position. A position matches only if *every* filter matched it. - **A game matches if at least one of its positions matched.** - After all games are processed, matching games are sorted (if the query has `sort` filters), comments are attached to matching positions, tag edits are applied, and the games are written to the output PGN. Consequences that trip people up: - Filters listed one after another are an implicit AND **at the same position**. `check mate` does not mean "there is a check somewhere and a mate somewhere"; it means "this position is both check and mate". - To ask a question about the *game* rather than about a position, anchor the query to a single position with `initial` or `terminal`, and use `find`, `echo`, or `line` to look at the other positions from there. Without an anchor, a whole-game query is evaluated (and may match, and may comment) once per position. - **Variations are not searched by default.** Add `cql(variations)` to the query or `--variations` on the command line to include them. `--mainline` forces them off. - Because evaluation short-circuits, put cheap, highly selective filters first. `mate` before an `echo` is much faster than the reverse. --- ## 2. RUNNING CQLi ``` cqli -i input.pgn -o output.pgn query.cql cqli -i input.pgn -o output.pgn --cql 'mate parent : check' ``` - `-i` / `--input` *file* — input PGN. `-o` / `--output` *file* — output PGN (truncated if it exists; use `-a`/`--append` to append). If `-o` is omitted a name is derived from the query file name. - The final non-option argument is the query file. A name with no extension is retried with `.cql` appended. Query files are looked up in the current directory and then in the semicolon-separated directories of the `CL_PATH` environment variable. - `--cql` *text* supplies the query inline. Multiple `--cql` options are concatenated; if a `.cql` file is also given, the `--cql` text is prepended to it. Quote the argument (single quotes in a POSIX shell) when it contains spaces, `$`, `|`, or other shell metacharacters. - Windows PowerShell 5.1 (the version included with Windows) strips double quotes from inside a quoted argument, so for that shell write each `"` in an argument (a `--cql` query, or a `%Q{...}` output file specifier) as `\"`: `--cql 'zobristkey == \"9f2e3a461655ff6b\"'`. PowerShell 7.3+, bash, and zsh need the plain `"` (a backslash there is an error). A query in a `.cql` file works in every shell, and `result 1-0` needs no quotes at all. - Options may be written with one or two hyphens and are case-insensitive (`--gamenumber`, `-gameNumber`, `--GameNumber` are the same option). Options worth knowing when writing a query: | Option | Effect | |---|---| | `--variations` / `--mainline` | Search variation positions, or force mainline only. | | `-g` / `--gamenumber` *range* | Only process games whose game number is in the range. | | `--limit` *n* | Stop after *n* matching games. | | `--matchcount` *range* | Only emit games whose number of matching positions is in the range. | | `--sortmatchcount` | Sort output games by number of matching positions, descending. | | `--matchstring` *s* | Text used to comment matching positions (default `CQL`). Empty string disables it. | | `--quiet` | Suppress match comments and auxiliary comments. | | `--silent` | Suppress *all* comments CQLi generates, including sort and header comments. | | `--noheader` | Suppress the per-game header comment. | | `-s` / `--singlethreaded`, `--threads` *n* | Control concurrency. | | `-w` / `--warnlevel` {1,2,3} | 1 = errors, 2 = + warnings, 3 = + infos. | | `--parse` | Print the parsed query tree and exit. Use it to check precedence. | | `--nosmartcomments` / `--alwayscomment` | Disable Smart Comments (see section 23). | | `--keepallbest` | Keep comments for every evaluation tied for best in `sort`, `line`, `consecutivemoves`. | | `--secure` | Reject queries containing `readfile`, `writefile`, `commandpipe`, or `import`, and ignore the header's `input`/`output`. | | `--elidecomments`, `--elidenags`, `--elidevariations`, `--pgnlinewidth` *n* | Output PGN formatting. | | `--showdictionaries` | Print dictionary contents at end of run along with other persistent variables. | | `--dryrun`, `--noclobber`, `--createdirectories` | For runs that write multiple output files. | **Filter injection options** add a filter to the query from the command line, so a stored `.cql` file can be reused with different restrictions: `--player`, `--white`, `--black`, `--event`, `--site`, `--year`, `--result`, `--fen`, `--btm`, `--wtm`, `--virtualmainline`, `--assign`, and the transform injections `--flip`, `--flipcolor`, `--fliphorizontal`, `--flipvertical`, `--reversecolor`, `--rotate45`, `--rotate90`, `--shift`, `--shifthorizontal`, `--shiftvertical`. Note that `--result` injects a `result` filter, whereas the header's `result` parameter filters games *before* the query runs, so the two are not interchangeable. Output file names may contain **dynamic specifiers** such as `%T{Event}` (value of a PGN tag) or `%Q{`*string-filter*`}` (value computed by the query), which splits matching games across files: ``` cqli -i input.pgn -o 'output/%T{Event}.pgn' --silent cqli -i input.pgn -o 'output/%Q{str gamenumber % 10}.pgn' --silent ``` --- ## 3. THE `cql()` HEADER An optional header sets front-end behavior from inside the query: ```cql cql(variations input games.pgn output hits.pgn matchcount 5 100) ``` | Parameter | Meaning | |---|---| | `variations` | Process variation positions. | | `input` *file* | Input PGN file. | | `output` *file* | Output PGN file; `stdout` writes to standard output. | | `gamenumber` *range* | Restrict processing to these game numbers. | | `matchcount` *range* | Only emit games with this many matching positions. | | `sort matchcount` *range* | As `matchcount`, and sort output by match count, descending. | | `matchstring` *string* | Comment text for matching positions; `""` disables. | | `result` *result* | Only process games with this result. Cannot be overridden by `--result`. | | `quiet` | Suppress match and auxiliary comments. | | `silent` | Suppress every comment CQLi adds. | Notes: command-line options override header parameters (except `result`). The header is optional, may appear anywhere a function definition may appear, and if several are present only the last takes effect. Put it on the first line anyway — CQL 6 requires that, and it reads better. Use `cql(silent)` for queries whose purpose is to edit tags or comments rather than to select games, otherwise every game picks up `{CQL}` markers. --- ## 4. TYPES, VALUES, AND WHAT "MATCHING" MEANS **Read this section carefully. It is the part of CQL that is unlike other languages, and it is where most incorrect queries go wrong.** Every filter has a static type and yields either a value of that type or the special value `None` (absence of a value). The types are: | Type | Values | |---|---| | *Boolean* | `true`, `false` | | *String* | UTF-8 text | | *Numeric* | 64-bit signed integer (no fractions; `/` truncates) | | *Set* | a set of zero or more board squares | | *Position* | a specific position in a game | | *Piece* | the identity of a particular piece (see section 22) | | *Dictionary* | key/value pairs | A value **matches the position** if it is *not* `None`, *not* `false`, and *not* the empty set. That is the entire rule. In particular: ``` None does NOT match false does NOT match [] does NOT match (the empty set) 0 DOES match (zero is a perfectly good number) "" DOES match (the empty string is a perfectly good string) ``` So `power A` (which yields `0` when White has only a king) always matches, and a query that means "White has no material" must be written `power A == 0`, not `not power A`. Yielding a value and matching are independent. Comparison filters, assignments, and string conversions can all produce a value that does not match. **Comparison filters do not return booleans.** `X < Y` yields the *value of X* if the comparison holds, and `None` if it does not. That is why comparisons chain: `$a < $b < $c` yields `$a` when `$b` lies between `$a` and `$c`. The one exception is `!=`, which is a true *Boolean* filter; `X != Y` is exactly `not X == Y`, so it is `true` when either side is `None` — including when both are. Implicit conversions: - A *Set* compared against a *Numeric* is converted to its **cardinality**. So `[Aa] == 3` means "there are exactly 3 pieces on the board", and `A attacks sq > 1` means "more than one white piece attacks sq". - A *Piece* variable is converted to a *Set* (the square it stands on, or the empty set if it has been captured) everywhere except as an argument to a user-defined function or to `str`, `comment`, `message`, or `pieceid`. --- ## 5. LEXICAL STRUCTURE - **Source comments**: `/* block */` (does not nest) and `// to end of line`. - **Boolean literals**: `true`, `false`. - **Numeric literals**: one or more digits. Negative values via unary `-`. - **String literals**: `"..."`. A string literal may not contain a double quote. There are **no backslash escapes inside string literals** — see section 9. - **Set literals**: piece designators such as `a1`, `a-h1-2`, `[a1-6,d5,f6]`, `Qd1`, `.` (all 64 squares), `[]` (no squares). - **Compound filters**: `{ ... }` groups a sequence of filters into one filter. `( ... )` groups for precedence. Both are free of runtime cost. - The shortest legal query is `.`, which matches every position of every game (equivalent to `true`). Identifiers (variable and function names): letters, digits, underscores and `$`, not starting with a digit; case-sensitive; unlimited length. They may not be a keyword (section 27) or anything that would parse as a piece designator. Names beginning with `__CQL` are reserved. **Convention: start every variable name with `$`.** `$count`, `$best_ply`, `$knight`. This is the single easiest way to avoid a name colliding with a piece designator or keyword, and it is what the manual recommends. A bare name like `b` is a piece designator (black bishops), not a variable; `Rank` would be read as a compound piece type designator inside brackets. --- ## 6. PIECE DESIGNATORS Piece designators are how you name squares and the pieces on them. They are the core of the language. A piece designator is a *piece type designator*, a *square designator*, or both (type first, then square). ### Piece type designators | | White | Black | |---|---|---| | King | `K` | `k` | | Queen | `Q` | `q` | | Rook | `R` | `r` | | Bishop | `B` | `b` | | Knight | `N` | `n` | | Pawn | `P` | `p` | | Any piece | `A` | `a` | | Empty square | `_` | | A bare type designator yields **the set of squares currently occupied by such pieces**: `Q` is "the squares white queens are standing on", `_` is "the empty squares", `a` is "all squares with a black piece". Combine types by bracketing them: `[Qq]` (queens of either color), `[RQ]` (white rooks and queens), `[_a]` (squares that are empty or hold a black piece), `[Aa]` (every occupied square). ### Square designators A *file designator* is a file letter `a`–`h`, or two joined by a hyphen. A *rank designator* is a rank digit `1`–`8`, or two joined by a hyphen. A simple square designator is a file designator immediately followed by a rank designator. ```cql a1 // one square a-h8 // the eighth rank d-e4-5 // the four centre squares b1-8 // the b file ``` **A square designator must have both parts.** `b` is the black-bishop *type* designator, not the b file; write `b1-8`. `8` alone is not valid; write `a-h8`. Combine squares by bracketing a comma-separated list: `[a1,h8]`, `[a1-8,a-h8,d4]`. ### Combining them ```cql Ra-h8 // white rooks on the 8th rank [Kk][a1,a8,h1,h8] // a king of either color in a corner Pe4 // a white pawn on e4 ``` The result is the **intersection** of the type part and the square part; a missing part means "all squares". So `Ra1` is exactly `R & a1`, and a bracketed list is a union: `[a1-8,a-h8,d4]` is `a1-8 | a-h8 | d4`, and `[Kk][a1,a8,h1,h8]` is `(K | k) & (a1 | a8 | h1 | h8)`. (Inside *shift* transforms the bracketed form behaves differently — see section 21.) `.` is all 64 squares and `[]` is none; `[]` is the complement of `.`. --- ## 7. OPERATORS ### Arithmetic (*Numeric* → *Numeric*) | Op | Meaning | Example | Result | |---|---|---|---| | `+` | addition | `4 + 5` | `9` | | `-` | subtraction, unary negation | `12 - 7` | `5` | | `*` | multiplication | `5 * 5` | `25` | | `/` | integer division (truncating) | `10 / 3` | `3` | | `%` | remainder | `10 % 3` | `1` | The result matches unless an operand fails to match, or the right operand of `/` or `%` is zero. There is no fractional type: scale instead (work in centipawns/decipawns if you need fractional piece values). Intrinsics: `abs` *x*, `sqrt` *x* (integer part; does not match for negative input), `max(`*a b* ...`)`, `min(`*a b* ...`)`. `abs` and `sqrt` take one unparenthesised argument; `max`/`min` need at least two arguments and require the parentheses. `max`/`min` ignore arguments that do not match. ### Comparison `==` `!=` `<` `<=` `>` `>=` Usable with two *Numeric*, two *String*, or two *Position* operands, or one *Numeric* and one *Set* (the set becomes its cardinality). `==` and `!=` additionally accept two *Set* operands. - `==`, `<`, `<=`, `>`, `>=` yield **the left operand's value** on success and `None` on failure, and are right-to-left associative, so they chain: `2000 <= year <= 2005`. - `!=` yields a *Boolean*. - *String* comparison is Unicode collation-aware. - **On *Position* operands, comparison tests ancestry**: `A < B` means A is an ancestor of B (A comes earlier on the same line), `A > B` means descendant, `<=`/`>=` allow A and B to be the same position. ### Logical `and`, `or` (binary infix), `not` (prefix). Both `and` and `or` **short-circuit**: `and` skips its right side when the left does not match, `or` skips its right side when the left matches. There is no `!` operator; use `not`. ### Set | Op | Meaning | Example | Result | |---|---|---|---| | `\|` | union | `a1 \| a8` | `[a1,a8]` | | `&` | intersection | `a1-8 & a-h3-4` | `[a3,a4]` | | `~` | complement | `~.` | `[]` | | `#` | cardinality | `#[c-f3-6]` | `16` | | `in` | subset test | `c3 in [a1,b2,c3]` | `true` | There is **no set-difference operator**. Write `A & ~B`. Symmetric difference is `(A & ~B) | (B & ~A)` or `(A | B) & ~(A & B)`. `A in B` is `true` when every square of A is also in B, i.e. `A & B == A`. For a proper subset use `A in B and A != B`. **Trap:** `A in B` is `true` when A is empty. A piece variable converts to the empty set once that piece has been captured, so `find Pawn in a-h8` matches after the pawn is captured as well as after it promotes. Use `&` instead: `find Pawn & a-h8`. `#` applied to a *String* gives the number of Unicode code points; applied to a *Dictionary*, the number of entries. ### Position operators **With-position, `:`** — `X : Y` means "evaluate Y as though the current position were X". X must be a *Position*; Y is any filter. The result has Y's type and value. If X is not a real position, Y is not evaluated at all and the whole thing yields `None`. `:` is right-to-left associative. ``` parent : check // was the previous position a check? position 0 : wtm // is it White to move at the start? $source : move legal enpassant // evaluate at the echo source position ``` **Positional intersection, `&` on two *Position*s** — yields the *Set* of squares that are the same in both positions (empty in both, or holding the same piece and color in both). `source & target == .` therefore means "these two positions have identical piece placement". ### Identity operator, `` ` `` A prefix operator that changes nothing about the value and everything about how the operand is *interpreted*: - suppresses pass-by-reference for a variable passed to a function: ``clear(`$var)`` cannot modify `$var`; - forces *Piece*-to-*Set* conversion inside `message`, `comment`, or a function call: with `piece King = K`, `message(K)` prints `Ke1` but ``message(`K)`` prints `e1`; - forces evaluation instead of stringification in `message`/`comment`: `message(event "X")` prints the Event tag then `X`, while ``message(`event "X")`` prints `true` or `false`; - stops square brackets from being parsed as a piece designator in a dictionary access: ``$Dx[`a2]``. ### Ranges Some filters take an optional *range*: one or two numeric filters, each a numeric **literal or numeric variable** (the first may be negated). One value means exactly that value; two mean the inclusive interval. Filters that accept ranges: `consecutivemoves`, `find`, `line`, the *direction* filters, and (deprecated) the *transform* filters. **Trap:** a range is grabbed greedily from the text that follows. `find 1 10 -3` parses as a `find` with range `[1:1]` and body `10 - 3`, not as range `[1:10]` with body `-3`. Wrap the body in braces or parentheses if it could be mistaken for a range. --- ## 8. VARIABLES A variable holds a value of any type **except Boolean**. Assignment declares the variable and fixes its type permanently. Referencing a variable before it is declared is a syntax error, checked at the point the reference is *processed* (function bodies are checked at invocation, not at definition). ```cql $count = 0 $squares = a-h1 $name = "Fischer" ``` Assignment `=` yields *Boolean* `true`, **unless the right-hand side is `None`**, in which case the variable is left alone and the assignment yields `false` and does not match: ```cql $check_pos = find check // fails to match if no later check exists ``` That behavior is idiomatic: an assignment doubles as a test. ### Conditional set assignment, `=?` `=` will happily store the empty set (and the assignment itself matches). `=?` assigns only when the right-hand side is a non-empty set: ```cql $X = [] // $X holds []; the assignment matches $X = a1 // $X holds a1 $X =? [] // $X unchanged; yields false, so the enclosing sequence fails ``` ### Compound assignment `+=` `-=` `*=` `/=` `%=` `|=` `&=`. As with `=`, a `None` right-hand side leaves the variable unchanged and does not match. `$s += $t` is far faster than `$s = $s + $t` for long strings; prefer it. ### Unbound variables A variable whose value is `None` is *unbound*. Any type except *Dictionary* can be unbound. `unbind $X` explicitly unbinds. `isbound `*name* yields `true` for a bound variable and `false` otherwise (including for a name that is not a variable at all); `isunbound` is its complement. `str` of an unbound variable is the string `""`. ### Persistent variables Ordinary variables reset to `None` at the start of every game. `persistent` makes a *Numeric*, *Set*, or *String* variable survive across games: ```cql persistent $total = 0 persistent $total_positions += 1 // declaration by compound assignment persistent quiet $log = "" // don't print this one at the end ``` Persistent variables are initialized once, before the first position of the first game: `0`, `[]`, or `""` respectively. Their final values are printed when processing ends, unless declared `quiet`. `unbind` on a persistent variable is permanent — it is never re-initialized. Under multiple threads, persistent variables are merged at the end (see the manual's "Persistent Variables and Merge Strategies"); use `-s` for strictly deterministic accumulation while developing. ### Dictionaries ```cql dictionary $D // String keys, String values (default) dictionary int --> set $Dx // Numeric keys, Set values dictionary str --> int $Counts ``` Key and value types are each `str`, `int`, or `set`, chosen at declaration; no initializer is given. **Dictionaries are always persistent** and may not be declared with the `persistent` keyword (but may be declared `quiet`). - `$D[`*key*`]` yields the value, or `None` if the key is absent. **Reading a missing key never creates it.** - `$D[`*key*`] = `*value* inserts or replaces, unless key or value is `None`. - `$D[`*key*`] =? `*value* (Set-valued dictionaries) assigns only a non-empty set. - Compound assignment **autovivifies**: `$D[`*key*`] += 1` creates the key with the default (`0`, `[]`, `""`) and then applies the operation. To increment only an existing key, use `if $D[`*key*`] then $D[`*key*`] += 1` or `$D[`*key*`] = $D[`*key*`] + 1`. - `unbind $D[`*key*`]` removes one key; `unbind $D` empties the dictionary (the variable itself is never `None`). - `#$D` is the number of entries. Iterate with the `key` filter (or `string` for String-keyed dictionaries). - `--showdictionaries` prints dictionary contents at the end of the run. **Trap — Set-keyed dictionaries and square brackets.** `$Dx[a2] += 1` is a syntax error: it parses as two filters, `$Dx` and `[a2] += 1`, because `[a2]` is a piece designator. Disambiguate one of three ways: ``` $Dx[ a2 ] // spaces $Dx[`a2] // identity operator $Dx[[a2]] // extra brackets (piece designators cannot nest brackets) ``` The same applies to a variable whose name is made only of the characters `QqBbRrNnKkPpAa_`: write `$Dx[ $Rank ]` or ``$Dx[`$Rank]``, not `$Dx[$Rank]`. ### Scope Variables live in the global scope or in a **block scope**. Block scopes are introduced by function invocations and by the iteration filters `echo`, `loop`, `piece`, `square`, `string`, `while`, and extend to the end of that filter. - Parameters (function parameters, and the iteration variables of `piece`, `square`, `string`, `echo`) always live in their block scope and shadow outer variables of the same name. - Other variables used inside a block either refer to an existing variable in an enclosing scope or create a new one in the current block. A variable created in a block is not accessible after the block ends. - Names in a function body resolve **when the function is invoked**, so a function may use a global declared later in the file — provided the declaration has been processed by the time the call happens. --- ## 9. STRINGS | Filter | Meaning | Example | Result | |---|---|---|---| | `+` | concatenation | `"hello " + "world"` | `"hello world"` | | `#` | length in code points | `#"hello"` | `5` | | `~~` | regex match/extract | `"XABACA" ~~ "(A.)+"` | `"ABAC"` | | `\`*n* | text of capture group *n* | `\1` | `"AC"` | | `\-`*n* | start index of group *n* | `\-1` | `3` | | `\{`*name*`}` | text of named group | `\{year}` | `"2024"` | | `\-{`*name*`}` | start index of named group | `\-{year}` | `0` | | `ascii` | char ↔ ordinal (0–127) | `ascii "A"` / `ascii 38` | `65` / `"&"` | | `in` | substring test | `"ll" in "hello"` | `true` | | `indexof(`*a b*`)` | index of *a* in *b* | `indexof("ll" "hello")` | `2` | | `int` | string → number | `int("0123")` | `123` | | `lowercase`, `uppercase` | Unicode-aware case | `uppercase "Strauß"` | `"STRAUSS"` | | `replace(`*subj pat repl* [*count*]`)` | regex replace | `replace("abcd" ".c" "X")` | `"aXd"` | | `str(`...`)` | convert and concatenate anything | `str(1 false "abc")` | `"1falseabc"` | `str` accepts one or more arguments of any type and always matches; the parentheses may be omitted for a single argument. `int` skips leading whitespace, accepts an optional sign, and ignores trailing non-digits; it does not match when no integer can be extracted. `indexof` returns the earliest occurrence and does not match if absent. `replace` replaces all matches when *count* is absent or `0`, the first *count* matches when positive, the last `-count` when negative. In the replacement string, `$1` and `${name}` insert capture groups, `\$` is a literal dollar, `\uxxxx`/`\UXXXXXXXX` insert Unicode characters. `replace`'s groups are *not* visible to the `\`*n* filters. ### String portrayal `str`, `comment`, and `message` convert values like this: | Type | Portrayal | Example | |---|---|---| | Numeric | decimal, leading `-` if negative | `-34` | | Set | bracketed, comma-separated, ascending rank-first | `[d4,e4,d5,e5]`, `[]` | | Boolean | `true` / `false` | `true` | | Piece | piece letter + square, or `[absent]` | `Ke2`, `re8`, `[absent]` | | Position | `move N(wtm)` / `move N(btm)`, plus `[id]` if not mainline | `move 4(wtm)[14]` | | Dictionary | `Dictionary with N entries:` then one `key: value` per line | | | `None` | `` (whatever the filter's type) | | ### Predefined strings (backslash filters) `\n` `\r` `\t` `\"` `\\` are *filters* yielding newline, carriage return, tab, double quote, backslash. **They only work outside string literals.** There are no escape sequences inside a CQL string literal: ```cql $X = "A" + \n // "A" followed by a real newline $X = "A\n" // "A", backslash, "n" — three characters ``` To put a double quote in a string, concatenate: `"he said " + \" + "hi" + \"`. ### Slicing `$x[`*i*`]` and `$x[`*m*`:`*n*`]`, zero-indexed over code points; negative indices count from the end (`$x[-1]` is the last character). Either end of a slice may be omitted. **The two forms fail differently:** - `$x[`*i*`]` yields `None` for an out-of-range index. - `$x[`*m*`:`*n*`]` always yields a string (possibly `""`); endpoints are clamped. It yields `None` only if `$x` itself does not match. ``` "abcde"[4] -> "e" "abcde"[1:] -> "bcde" "abcde"[5] -> None "abcde"[:3] -> "abc" "abcde"[-5] -> "a" "abcde"[-4:-1] -> "bcd" "abcde"[-6] -> None "abcde"[-10:10] -> "abcde" "abcde"[1:1] -> "" "abcde"[10:20] -> "" ``` First *n* characters: `$x[:`*n*`]`. Last *n*: `$x[-`*n*`:]`. Slice assignment `$x[`...`] = `*string* replaces that part of the variable, growing or shrinking it. `$x[`*i*`] = ...` fails for an invalid index; `$x[`*m*`:`*n*`] = ...` can succeed while changing nothing. ### Regular expressions `~~` and `replace` use ICU regular expressions (ICU 78+): the usual `.`, `*`, `+`, `?`, `{m,n}`, `[...]`, `|`, `(...)`, `(?:...)`, `\d`, `\w`, `\s`, backreferences, lookahead and lookbehind, named groups `(?...)`. - Matching is **case-sensitive and unanchored** by default. `^` and `$` anchor; by default they also match at line boundaries (flag `m` is ON). - Inline flags: `(?i)` case-insensitive, `(?s)` `.` matches newlines, `(?m)`, `(?w)`, `(?x)` extended/commented. Turn off with `-`: `(?i)Michael (?-i)Jones`. Scope to a group with `(?i:...)`. - A malformed pattern given as a **string literal** is a compile-time error; a malformed pattern computed at runtime is a **fatal runtime error**. Literal patterns are also faster, since they compile once. - `~~` yields the matched substring, and does not match the position if there is no match. - The `\`*n* group filters refer to **the most recently evaluated `~~`**, and yield `None` if there was none, or the group did not participate. **Regex iteration.** When the condition of `while` is a `~~` filter, `while` iterates over every match: ```cql while ("ABC" ~~ ".") { message \0 } // prints A, then B, then C ``` The left side is evaluated once; the pattern is compiled once even if it is a variable that later changes. Use `"\X"` instead of `"."` to iterate grapheme clusters. After the final iteration the `\`*n* filters yield `None`. --- ## 10. BOARD STATE FILTERS ### `attackedby` and `attacks` Both are binary infix filters taking *Set* on each side. ``` X attackedby Y → Set: the squares of X that are attacked by pieces in Y X attacks Y → Set: the squares of X holding a piece that attacks a square in Y ``` They match under exactly the same conditions but return different things: `k attackedby A` returns the black king's square; `A attacks k` returns the squares of the white pieces giving check. A piece P attacks square S if an enemy king on S would be in check from P. **P attacks S even when P is pinned, even when its own king is in check, and even when it is not P's turn.** Use `pin` to exclude pinned attackers. `attackedby`/`attacks` bind **tighter** than `&`, `|`, `~`, `#`, and the comparison filters, so these need no parentheses: ``` . attackedby A // squares White attacks a attackedby A & ~ a attackedby a // undefended black pieces attacked by White #A attacks $sq > a attacks $sq // $sq is attacked more than it is defended a-h5-8 & (. attackedby A & ~ . attackedby a) // Black's half, attacked but undefended ``` CQL 6.1 also spells `attackedby` as two tokens, `attacked by`; CQLi accepts that for compatibility. Prefer the one-word form. ### Side to move `wtm` — `true` if White to move. `btm` — `true` if Black to move. `sidetomove` — *Numeric*, yields `white` (`1`) or `black` (`-1`). `white` and `black` are *Numeric* constants `1` and `-1`. Use `sidetomove` when comparing two positions: `sidetomove != source:sidetomove`. ### `check`, `mate`, `stalemate` *Boolean*, about the **side to move**. Equivalents in standard chess: ``` check is equivalent to flipcolor { wtm and K attackedby a } mate is equivalent to check and move legal count == 0 stalemate is equivalent to not check and move legal count == 0 ``` `mate` with `btm` therefore means "White has just delivered mate". ### `colortype` and `type` Each takes one *Set*. If it is not exactly one square, the filter does not match. Otherwise it yields the piece type on that square: | empty | pawn | knight | bishop | rook | queen | king | |---|---|---|---|---|---|---| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | `colortype` negates the value for black pieces (white rook `4`, black rook `-4`); `type` gives `4` for both. Chiefly used to compare what stands on two squares or in two positions. ### `power` `power `*Set* → *Numeric*: the total value of pieces on those squares. | K | P | N | B | R | Q | |---|---|---|---|---|---| | 0 | 1 | 3 | 3 | 5 | 9 | Always matches; yields `0` for an empty set or kings only. ```cql power a - power A >= 8 // Black is at least 8 points ahead ``` For different piece values, write a function and scale to integers: ```cql function altPower($X) { #[Pp]&$X * 10 + #[Nn]&$X * 30 + #[Bb]&$X * 35 + #[Rr]&$X * 55 + #[Qq]&$X * 100 } ``` ### FEN filters - `standardfen` — *String*, full FEN of the current position. - `currentfen` — same but with halfmove clock forced to `0` and move number to `1`, so identical placements produce identical strings. Better as a dictionary key. - `fen "`*placement*`"` — *Boolean*, matches when the board matches the given **piece-placement field** (the part before the first space). The characters `A`, `a`, `.`, and `_` may be used with their CQL meanings in addition to normal FEN characters. The literal is validated at parse time. It **is** transformed by enclosing `flipcolor`, `reversecolor`, and dihedral transforms. - `fen` not followed by a string literal behaves as `currentfen`. ```cql fen "k7/8/NKB5/8/8/8/8/8" flipcolor flip fen "k7/8/NKB5/8/8/8/8/8" // all 16 orientations/colorings ``` Castling rights use X-FEN (file letters when the castling rook is not in the corner, as in Chess960). The en passant square is set whenever the last move was a double pawn push, whether or not a capture is available. Crazyhouse adds a bracketed pocket list and `~` on promoted pieces; Three-Check adds a `D+d` field. Piece designators are usually clearer than `fen`; use `fen` when you have a FEN string in hand. ### Clocks and counters - `halfmoveclock` — plies since the last capture or pawn move. Honours a `FEN` tag. `halfmoveclock == 100` plus `move legal` finds the moment a fifty-move claim becomes available. - `movenumber` — the move number; increments after Black's move; honours the `FEN` tag's starting number. Always matches. - `ply` — half-moves since the initial position, starting at `0`. Not affected by the `FEN` tag's move number. `ply == 200` matches the position at ply 200; `ply >= 200` matches every later position too (and comments all of them). To talk about game length, anchor: ```cql terminal ply >= 200 ``` Even ply is usually White to move, but not if a `FEN` tag starts the game with Black to move — use `wtm`/`btm` when it matters. ### Pawn structure Each yields a *Set* covering **both colors**; intersect to pick a side. - `connectedpawns` — pawns with a friendly pawn on an adjacent file. - `isolatedpawns` — pawns with no friendly pawn on an adjacent file. - `doubledpawns` — two or more friendly pawns on one file (not necessarily adjacent ranks). - `passedpawns` — no enemy pawn ahead on the same or an adjacent file. ```cql passedpawns & p // black passed pawns doubledpawns & a-d1-8 // queenside doubled pawns connectedpawns & passedpawns // connected passed pawns ``` Every pawn is either isolated or connected, so `isolatedpawns & connectedpawns` is always `[]`. Other structures, written out: ```cql flipcolor Pa-h5-8 // advanced pawns (past the 4th rank) flipcolor p & up 1 P // fixed pawns (blocked head to head) flipcolor P & diagonal 1 P // pawn chains (flipcolor P & diagonal 1 P) & (flipcolor P & ~ up horizontal 0 1 P) // the bases of pawn chains shifthorizontal flipcolor { $TP = down a8 & P $TP > 2 $TP } // tripled pawns ``` ### Other board filters - `promotedpieces` — *Set* of squares holding promoted pieces. `promotedpieces & Q` and `promotedpieces & q` finds positions where both sides promoted. - `zobristkey` — *String*, 64-bit Zobrist hash in hex. Covers piece placement, castling rights, en passant rights, side to move (and Crazyhouse pockets), but **not** ply, move number, or halfmove clock — which makes it the natural key for repetition detection. Polyglot-compatible for standard chess. --- ## 11. BOARD GEOMETRY FILTERS ### Direction filters ``` direction [range] Set → Set ``` The set of squares reachable by stepping in that direction from any square of the operand. The default range is `1 7`. Including `0` in the range keeps the original squares; negative values step the opposite way. Basic: `up` `down` `left` `right` `northeast` `northwest` `southeast` `southwest`. Compound: `vertical` (up+down), `horizontal` (left+right), `orthogonal` (vertical+horizontal), `maindiagonal` (NE+SW), `offdiagonal` (NW+SE), `diagonal` (both diagonals), `anydirection` (everything). ```cql up 1 P // the squares directly in front of white pawns up 0 7 a1 // a1 and everything above it down horizontal 0 1 p // squares beside and below black pawns ``` Directions are relative to the board as printed (White at the bottom), and are rewritten by dihedral and `rotate45` transforms. ### Others - `between(`*set1 set2*`)` — *Set* of squares lying on a straight line strictly between a square of *set1* and a square of *set2*. - `dark `*Set* / `light `*Set* — the dark or light squares within the set. Transposed by transforms that change a square's color. - `file `*Set* / `rank `*Set* — *Numeric* 1–8 for a one-square set, otherwise `None`. File `a` is `1`, rank `1` is `1`. Not affected by transforms. - `makesquare("`*e4*`")` or `makesquare(`*file rank*`)` — the *Set* for that square, or `[]` if invalid. Not affected by transforms. ``` max( abs(file $sq1 - file $sq2) abs(rank $sq1 - rank $sq2) ) // king-move distance ``` --- ## 12. RAY FILTERS A *ray* runs from a starting square in one of the eight basic directions. ### `ray` ``` ray [direction ...] ( Set1 Set2 ... ) → Set ``` Finds rays that start on a square in *Set1* and, moving in one of the given directions, hit a square of each successive set in order, with **only empty squares allowed in between**. Default direction is `anydirection`. Two or more sets are required. The result is the set of squares where matching rays **end** — reverse the argument order (and the direction) to get the starting squares. ```cql ray (A a A a) // four pieces in a line, alternating color ray diagonal (B n k) // white bishop pinning a black knight ray up (R [Pp] r) // rooks facing each other on a file through one pawn ray orthogonal (R R q k) // rook battery pinning the black queen to its king ``` Because only empty squares may separate the sets, `ray(r r)` does not match two black rooks with the black king between them. Use `_` explicitly for gaps: `ray(b _ . _ A)`. ### `xray` ``` xray ( Set1 ... Setn ) → Set ``` Like `ray`, but *Set1* must hold a **sliding piece that could move along the ray** — that is, `Set1` is implicitly intersected with `[RrQq]` for orthogonal rays and `[BbQq]` for diagonal ones. Colours are irrelevant, and only *Set1* must actually be occupied. The result is where matching rays end. ```cql xray(B r q) // a bishop x-raying a rook onto a queen flipcolor xray(B k [qr]) // a bishop x-raying the king onto queen/rook // rook x-rays a knight onto the enemy queen, where the knight can safely check xray(R (move from N to ~(. attackedby a): check) q) ``` Equivalent to `ray orthogonal ((Set1&[RrQq]) ...) | ray diagonal ((Set1&[BbQq]) ...)`. ### `pin` ``` pin [from Set] [through Set] [to Set] → Set ``` A pin exists from Px on Sx through Py on Sy to Sz when: Px attacks Py; Px and Py are opposite colors; Sz is not attacked by Px but would be if Py were gone; and Sz is empty or holds a piece of Py's color. Only sliding pieces can pin. Px counts as attacking even if Px is itself pinned. Defaults: `from [Aa]`, `through [Aa]`, `to [Kk]` — i.e. bare `pin` finds absolute pins. **The result depends on which parameter is written first:** | First parameter | Result | |---|---| | `from` | squares of the **pinning** pieces | | `to` | squares the pinned pieces are **pinned to** | | `through`, or none | squares of the **pinned** pieces | ```cql pin // pieces pinned to their king pin from [Aa] // the pinning pieces, same pins pin to . // every pin on the board, including onto empty squares pin through [bn] from (R attackedby K) to [Kk] ``` A king may appear in the `through` set (what would normally be called a skewer). --- ## 13. METADATA FILTERS ### `result` `result 1-0` / `result 0-1` / `result 1/2-1/2` / `result *`, or the same as string literals `result "1-0"` etc. → *Boolean*. This reads the **game termination token**, not the `Result` tag. For the tag, use `tag "Result"`. In CQLi (unlike CQL 6.1) `1-0` is not a single token, so `result 1 - 0` means the same as `result 1-0`. ### Standard tag filters `date` (`Date`, else `UTCDate`), `eco` (`ECO`), `event` (`Event`), `eventdate` (`EventDate`), `site` (`Site`), `player` (`White` and `Black`). Bare, each yields the tag's *String* value, and **does not match if the tag is absent**. Followed immediately by a string literal, each becomes a *Boolean* substring test: `site "Reykjavik"` is `"Reykjavik" in site`. `player white` and `player black` select one tag; bare `player` is `notransform { player white + \n + player black }`. `player` and `elo` are transposed by color transforms. ### `year` and `elo` `year` — *Numeric* year from `Date` or `UTCDate`; does not match if unavailable. `2000 <= year <= 2005`. `elo` — *Numeric* rating. `elo white` / `elo black` for one player; bare `elo` gives the higher of the two and does not match unless both are present. Ratings are read from the first present numeric tag among `WhiteElo`/`BlackElo`, `WhiteRating`/`BlackRating`, `WhiteRapid`/`BlackRapid`, `WhiteICCF`/`BlackICCF`, `WhiteUSCF`/`BlackUSCF`, `WhiteDWZ`/`BlackDWZ`, `WhiteBCF`/`BlackBCF`. ### `tag`, `settag`, `removetag` - `tag "`*Name*`"` → *String*, or `None` if absent. Always reads the **original** PGN, never the effect of `settag`/`removetag`. - `settag("`*Name*`" `*value*`)` → sets or creates the tag in the output. Tag names must be `[A-Za-z0-9_]+`, otherwise the filter yields `false` and changes nothing. Line breaks in the value become spaces. - `removetag "`*Name*`"` → removes the tag from the output; always yields `true`. A later `settag` for the same name wins. Neither `settag` nor `removetag` uses Smart Comment semantics: once evaluated, the change sticks even if the position ultimately fails to match. Guard them with `if` or with earlier filters. Setting or removing `FEN`/`SetUp` will confuse other PGN software. ### `gamenumber` *Numeric* one-based index of the game in the input file, correct even under multiple threads. With `--gamenumber 10 20`, values run 10–20 (indices are not renumbered). --- ## 14. GAME TREE FILTERS ``` ancestor( Position Position ) → Boolean (deprecated; use A < B ) descendant( Position Position ) → Boolean (deprecated; use A > B ) child → Position the primary child child( Number ) → Position the nth child (0 == primary) currentposition / curpos → Position depth → Numeric variation depth distance( Position Position ) → Numeric initial → Boolean initialposition → Position == position 0 lca( Position Position ) → Position latest common ancestor mainline → Boolean parent → Position position Number → Position by position ID positionid → Numeric terminal → Boolean variation → Boolean virtualmainline → Boolean ``` Definitions: - The **initial position** has ID 0 and no parent. Every other position has exactly one parent and zero or more children. - The first move recorded at a position is the **primary move**; the rest are **secondary** and start variations. Positions reachable using only primary moves are **mainline**; the rest are **variation** positions. - **Variation depth** is the number of secondary moves traversed to reach a position (equivalently, the number of unclosed parentheses in the PGN). `mainline` == `depth == 0`. - **Terminal**: no children. `terminal` == `not child`. **Initial**: `not parent` == `positionid == 0`. - **LCA**: the latest position that is a generalised ancestor of both. **Distance**: the number of moves from each position to the LCA, summed. - **Virtual mainline**: mainline, or reachable from the mainline without taking a secondary move at a White-to-move position (i.e. Black's alternatives are tolerated). Ancestry is tested with the comparison operators on *Position* values: `A < B` (A precedes B on one line), `A > B`, `A <= B`, `A >= B`. Number of moves recorded at the current position: ```cql currentposition: move count ``` The `currentposition:` prefix matters inside a `line` filter, where linearization would otherwise hide all but the move on the current line. --- ## 15. SEARCHING OTHER POSITIONS: `find`, `echo`, `consecutivemoves` ### `find` ``` find [quiet] [<--] [all | range] target-filter ``` Evaluates *target-filter* at the current position and then at each **descendant** position, in increasing position-ID order, stopping at the first match. - Result: the first matching *Position*, or `None`. - `<--` searches **ancestors** instead (in decreasing position-ID order). - `all` searches everything and yields the **number** of matches (or `None` if none). - A *range* instead of `all` yields the count if it falls in the range, else `None`. - `quiet` suppresses the automatic `Found` comments. `quiet`, `<--`, and `all` may appear in any order but must precede the range. Idioms: ``` initial ... find ... ask a forward question once per game terminal ... find <-- ... ask a backward question once per line ``` ```cql // games where neither side castled terminal not find <-- move castle // games with more than 5 promotions on one line terminal sort "Number of promotions" find <-- all move promote A > 5 // most captures by a single piece initial piece $p in [Aa] { sort "Captures by one piece" { find all move previous capture . from $p } >= 10 } // most captures on one square initial square $sq in . { sort "Captures on one square" { find all move previous capture $sq } >= 10 } ``` Without an anchor like `initial`/`terminal`, a `find` runs once per position, which is both slow and usually not what was meant. ### `echo` ``` echo [quiet] ( source target ) [in all] target-filter ``` Creates a block scope with two *Position* variables. *target-filter* is evaluated once for **every position in the game except the current one** (`in all` includes the current one too), with *source* bound to the current position and *target* to the one being visited, in position-ID order. - If *target-filter* is *Numeric*, `echo` is *Numeric* and yields the **largest** value seen. Otherwise `echo` is *Boolean* and matches if any evaluation matched. - The names `source` and `target` are conventional, not required. - As the target of a `sort` filter, `echo` gets special treatment: only the comments from the evaluation producing the best value survive. Because numeric `echo` maximises, it may not be the target of `sort min`. ```cql // two positions identical except for the side to move echo (source target) { source & target == . sidetomove != source:sidetomove } // two positions differing only in whether en passant is available move legal enpassant echo (source target) { not move legal enpassant source & target == . sidetomove == source:sidetomove source:move legal enpassant: comment("enpassant capture " currentmutation " legal here") comment("enpassant capture not legal here") } ``` `echo` is the right tool when the relationship between two positions does not imply an order, or when the pair may straddle variations; `find` is better when one position must precede the other. **Performance.** `echo` re-scans the whole game every time it is reached. Put cheap gating filters in front of it (as `move legal enpassant` does above) so it runs at few positions. An `echo` can be approximated with `find`, which is sometimes more flexible: ``` $source = currentposition initialposition : find quiet all { $target = currentposition $source != $target ... } ``` ### `consecutivemoves` ``` consecutivemoves [quiet] [range] ( position1 position2 ) → Numeric ``` The length of the longest run of identical moves shared by the two lines bounded by those positions and their LCA. Two moves count as identical when the from-square, to-square, moved piece type and color agree — so promotions to different pieces, and captures versus non-captures, are treated as the same move. Yields `None` if either argument is `None`, or if one position is the LCA of the other. At most one sequence pair is annotated per game. Usually paired with `echo`, and used on endgame studies. --- ## 16. `sort` ``` sort [quiet] [min|max] [description] target-filter ``` - *target-filter* may be *String*, *Numeric*, or *Set* (a set becomes its cardinality). The `sort` filter's own result is that value. - Across all evaluations in one game, CQLi tracks the **best** value: largest unless `min` is given. *description* is a string literal used to label the comment; one is generated if omitted. - Every matching game gets a **sort comment** at the top — `{description: value}` — and the output PGN is **sorted by that value** (ascending for `min`, descending otherwise), with game number as tie-break. - Comments generated while evaluating the target are discarded except for the evaluation that produced the best value (this is Smart Comments; see section 23). `--keepallbest` retains all evaluations tied for best. - `quiet` suppresses the sort comment. `--silent` suppresses it too. Several `sort` filters give successive sort keys, in the order written. Two `sort` filters that share a *description* are conjoined into one ordering and one comment; they must agree on direction and type. `sort` filters inside a transform are automatically conjoined, description or not. A `sort` that is never reached, or never matches, has an empty best value that sorts after every real value regardless of direction. ```cql sort "Consecutive checks" { line singlecolor nestban --> check + } >= 10 terminal flipcolor result 1-0 sort "Total moves" movenumber > 100 cql(silent) initial player "Anand" sort min tag "Date" ``` --- ## 17. THE `move` FILTER `move` reports on moves; it does not make them (unless used speculatively, see section 22). ``` move [mode] [constraints ...] [count] ``` ### Modes (at most one) | Mode | Moves considered | |---|---| | *(none)* | the moves actually recorded at this position, including moves that start variations | | `previous` | the single move that led to this position | | `legal` | all legal moves in this position | | `pseudolegal` | all pseudolegal moves (may leave one's own king in check) | | `reverse` | moves that could conceivably have been played to reach this position; must be combined with `legal` or `pseudolegal` | ### Constraints | Parameter | Argument | Restricts to | |---|---|---| | `from` | *Set* | moves whose from-square is in the set | | `to` | *Set* | moves whose to-square is in the set | | `capture` | *Set* | moves capturing a piece standing in the set | | `promote` | piece type designator | promotions to that piece | | `drop` | piece type designator | Crazyhouse drops of that piece | | `enpassant` | — | en passant captures | | `castle` | — | any castling | | `o-o` | — | kingside castling | | `o-o-o` | — | queenside castling | | `null` | — | null moves (`--`, `Z0`, `null`, `pass`) | | `primary` | — | moves that do not start a variation | | `secondary` | — | moves that start a variation | | `count` | — | makes the filter *Numeric* (see below) | `promote` and `drop` take a bare type designator with no square part; color is ignored (`promote N` == `promote n`), and `A` means "any". To constrain the square, add `to`: `move to a-h8 promote B`. Compound designators are allowed: `move promote [BNR]` (underpromotions). Square semantics: castling and null moves are king moves, so their from/to squares are the king's; a null move's from and to are the same. For an en passant capture, the *to* square is where the capturing pawn ends up and the *capture* square is where the captured pawn stood. Drops have no from-square, so `drop` cannot be combined with `from` — and specifying `from .` is the way to *exclude* drops. ### Result type — the rule that catches people 1. If `count` is present → **Numeric**, the number of matching moves. Always matches (yields `0` when nothing matches). 2. Otherwise, if the **first** parameter written is `from`, `to`, or `capture` → **Set**, the from-, to-, or capture-squares of all matching moves. 3. Otherwise → **Boolean**, matching when at least one move matches. So `move from . previous` is a *Set* (`from` came first) but `move previous from b4` is a *Boolean* (`previous` came first). Parameter order does not change *which* moves match — only the result type. ### `previous` and where its sets are evaluated With `previous`, the *Set* arguments of `from`, `to`, and `capture` are evaluated **in the preceding position**. That is why ```cql move previous from B ``` works: `B` is read before the bishop moved. If it were read in the current position it would name the bishop's destination. ### Constraints checked at compile time - at most one castling specification (`move castle o-o` is an error); - no parameter twice; - not both `primary` and `secondary`; - `primary`/`secondary` not with `legal`/`pseudolegal`; - at most one of `previous`, `legal`, `pseudolegal`; - `reverse` only with `legal` or `pseudolegal`; - `drop` not with `reverse`, `null`, `from`, `capture`, `promote`, `enpassant`, or a castling specification. ### Trailing `comment` A `comment` (or `message`) written immediately after an **ordinary** `move` filter is adopted by it and evaluated at the position *after* the matching move: ```cql move o-o-o comment "queenside castle" // comment lands after castling ``` Suppress that by bracketing either filter, or by putting the comment first: ```cql move o-o-o (comment "before castling") (move o-o-o) comment "before castling" comment "before castling" move o-o-o ``` ### Examples At a position where Black has just played `4...Bxc3+` and White will reply `5.bxc3`: | Filter | Result | |---|---| | `move` | `true` | | `move count` | `1` | | `move from . previous` | `b4` | | `move to . previous` | `c3` | | `move capture . previous` | `c3` | | `move previous from b4` | `true` | | `move legal count` | `4` | | `move from A legal` | `[c1,d1,e1,b2]` | | `move to _ legal` | `[d2,e2]` | | `move capture . legal` | `c3` | | `move legal castle` | `false` | | `move pseudolegal count` | `35` | | `move legal reverse count` | `2` | | `move from . legal reverse` | `[b4,a5]` | More: ```cql move from R // a rook move was played here move legal castle // castling is available here move promote [BNR] capture . // an underpromotion with capture move previous castle // the previous move was castling halfmoveclock == 100 move legal // a fifty-move claim just became available ``` Reverse move generation assumes standard chess and guarantees only that the prior position would be *legal*, not that it is *reachable*; combine with `reachableposition` (section 22) to filter. If the en passant square is set, reverse generation assumes the last move was the corresponding double push. --- ## 18. THE `line` FILTER `line` matches a **sequence of consecutive positions** starting at the current one, using regex-style repetition. It is the most powerful filter in CQL. ``` line [parameters] [range] --> constituent [quantifier] --> constituent ... line [parameters] [range] <-- constituent [quantifier] <-- constituent ... ``` - Each constituent is an arbitrary filter, introduced by `-->` (forward, through child positions) or `<--` (backward, through parents). **You may not mix the two arrows in one `line` filter.** - Each `-->`/`<--` consumes one position, including inside a group. - The filter matches when every constituent matches its position the required number of times, and yields the **length of the longest matching sequence** (a *Numeric*), or the last position when `lastposition` is given. ```cql line --> check --> move previous capture (flipcolor A attacks k) --> mate ``` ### Quantifiers | Quantifier | Meaning | |---|---| | `?` or `{?}` | zero or one | | `*` or `{*}` | zero or more | | `+` or `{+}` | one or more | | `{`*n*`}` | exactly *n* | | `{,`*n*`}` | up to *n* | | `{`*n*`,}` | *n* or more | | `{`*m*`,`*n*`}` or `{`*m n*`}` | between *m* and *n* | Quantifiers are **greedy but not at the cost of failing the match**, so ```cql line --> move previous capture . + --> mate ``` still matches when the mating move is itself a capture: the `+` gives back the final position so `mate` can have it. **If a `*` or `+` could be read as multiplication or addition, it will be.** Write `{*}` and `{+}` to be unambiguous — and they read better. Counted repetition bounds must be non-negative literals ≤ 9999, with *m* ≤ *n*; counted repetitions expand internally and nested ones multiply, with a combined limit of 100000 constituents. Both limits are diagnosed at parse time. ### Grouping Parenthesise constituents to repeat them as a unit: ```cql line --> (check --> move previous capture (flipcolor A attacks k)) {3,} --> mate ``` Groups nest, and inner constituents may carry their own quantifiers. Remember that every arrow inside a group still consumes a position: the query above needs at least seven positions. ### Parameters | Parameter | Effect | |---|---| | `firstmatch` | Take the **shortest** matching sequence rather than the longest. Cannot combine with `max` or `keepallbest`. | | `focus` *filter* | Only consider positions matching *filter*; others are skipped without breaking the sequence and without counting toward its length. The starting position must satisfy it. | | `keepallbest` | Retain every sequence tied for best, not just one. | | `lastposition` | Yield the final *Position* instead of the length. | | `max` *numeric-filter* | Choose the sequence maximising *numeric-filter* evaluated at its last position, instead of the longest. | | `nestban` | Ban the positions of a matched sequence from starting a later sequence in the same game. Forward (`-->`) filters only. | | `nolinearize` | Turn off move linearization in all constituents. | | `nonatomic` | Turn off atomic evaluation of variable changes in constituents. | | `piecepath` *set-filter* | Comment the end of each retained sequence with the squares each designated piece visited (`{Path of Ng1: g1-f3-g5}`). | | `primary` | After the first position, ignore positions that start a variation. | | `quiet` | Suppress the automatic begin/end comments. | | `secondary` | After the first position, only consider positions that start a variation. | | `singlecolor` | Only consider positions with the same side to move as the starting position. | | `title` *string* | Name the sequence in the automatic comments. | | `verbose` | Comment each matched position with its step number (`{Position 2 of 5}`). | `focus`, `max`, `title`, `verbose`, `piecepath`, `keepallbest`, `nolinearize`, and `nonatomic` are CQLi extensions (not in CQL 6.1). ```cql line singlecolor --> check {10,} // one side checked 10+ times running line focus wtm --> check {3,} // White checked 3+ times running line nestban --> move capture . {5,} // runs of 5+ captures, no sub-runs reported // the capture run that ends with the best material balance for White line max power A - power a --> move capture .{+} ``` `singlecolor` is the common case for "in a row by one side"; `focus` generalises it. Without `nestban`, every tail of a long match is reported as well, which is almost never wanted. ### Automatic comments Unless `quiet`, the sequence start gets `Start line that ends at move `*pos* and the end gets `End line of length `*n*` that starts at move `*pos*. `title` renames both. `verbose` and `piecepath` comments are *not* suppressed by `quiet` (only by `--quiet`). ### Move linearization Inside a `line` constituent, an **ordinary** `move` filter (no `previous`, `legal`, or `pseudolegal`) sees only the move that leads to the next position of the sequence being tested, as if the other recorded moves did not exist. Without this, `line --> move to e4 --> move to d5` would match a game where `e4` and `d5` were played in different variations. Disable per-filter or with `nolinearize`; use `currentposition:` to look at all moves anyway. CQL 6.1 does not linearize backward (`<--`) lines; CQLi does. ### Atomic evaluation By default, variable modifications made by constituents are applied atomically, so a `focus` expression always sees the variable state from before the `line` began, and backtracking cannot leave variables half-updated. `nonatomic` turns this off. ### Ambiguity When several sequences tie for best, the one ending at the lowest position ID is selected for comments, `lastposition`, and final variable state; `keepallbest` keeps the comments of all of them. When several constituents carry quantifiers, which constituent matched which position is unspecified — relevant only if the constituents have side effects. --- ## 19. SELECTION AND ITERATION ### `if` ``` if condition [then] true-filter [else false-filter] ``` `then` is optional. Type and value: - with no `else`: *Boolean*, `true` if *true-filter* matched **or was not evaluated** (because the condition failed); - with `else` and both branches the same type: that type, with the value of whichever branch ran; - with `else` and branches of different types: *Boolean*, matching if the branch that ran matched. | Example | Result | Why | |---|---|---| | `if 1 then 2` | `true` | no `else` ⇒ Boolean | | `if a1 then false` | `false` | condition matched, branch did not | | `if false then false` | `true` | condition failed, so nothing to fail | | `if true then 2 else 3` | `2` | same types | | `if true then 2 else K` | `true` | different types ⇒ Boolean | `if` chains naturally: ```cql if result "1-0" then comment "White won" else if result "0-1" then comment "Black won" else if result "1/2-1/2" then comment "Draw" else comment "Incomplete" ``` `if` is also an expression: `$side = if wtm then "white" else "black"`. ### Iteration filters All of them open a new block scope that ends with the filter. ``` square [all] $var in Set-Expression Body piece [all] $var in Set-Expression Body key $var in DictionaryName Body string $var in DictionaryName Body while ( Condition ) Body loop Body ``` - **`square`** evaluates *Set-Expression* once, then runs *Body* with `$var` bound to each square in turn. With `all`, the result is *Boolean* `true` only if *Body* matched every time. Without `all`, the result is the **set of squares for which Body matched**. An empty set means *Body* never runs; the result is `true` with `all`, `[]` without. Iteration order is unspecified. - **`piece`** is the same but iterates only over **occupied** squares, binding `$var` to the *piece identity* (which follows the piece as it moves, and survives promotion). Same `all` semantics. - **`key`** iterates dictionary keys; **`string`** does the same for String-keyed dictionaries and exists for backward compatibility. Order is unspecified. - **`while`** re-evaluates *Condition* then *Body* as long as *Condition* matches. Always matches, except in the regex-iteration form (section 9) when the left side of `~~` does not match. **The loop does not stop because *Body* failed** — and if a filter earlier in *Body* fails, the rest of *Body* (including your loop-variable update) never runs, so update loop variables first. - **`loop`** runs *Body* until it stops matching. ```cql square all $sq in d-e4-5 { #A attacks $sq > a attacks $sq } piece $pi in A { #a attacks $pi > A attacks $pi } flipcolor piece $Pawn in P { [bnrqk] attackedby $Pawn > 1 } $overdefended = flipcolor square $sq in . { a attacks $sq < A attacks $sq > 1 } ``` `piece $X in Set Body` is exactly `square $W in Set & [Aa] { piece $X = $W Body }`. --- ## 20. FUNCTIONS ``` function name( [$p1 $p2 ...] ) { body } name( arg1 arg2 ... ) ``` Functions are **inlined textually at each call site**. Consequences: - **No recursion.** A function may not call itself. - Parameter types are not declared; they are deduced per call, so the same function may be used with different argument types. Most errors in a function body are only diagnosed **at the first invocation**, and are reported with a "While instantiating function" note. - Parentheses are required even with no arguments: `myFilter()`. - Arguments are separated by whitespace, **not commas**. - **Variables are passed by reference**, so a function can modify a caller's variable. Pass by value by wrapping the argument in parentheses/braces, or by prefixing the identity operator: `` clear(`$var) ``. - Function bodies are affected by transforms surrounding the **call site**. Wrap the body (or part of it) in `notransform` when that is unwanted. - Parameters shadow outer variables of the same name. Names in the body resolve at invocation, so a global declared later in the file is visible provided it has been processed by then. ```cql function XOR($a $b) { ($a & ~$b) | ($b & ~$a) } XOR(. attackedby A . attackedby a) function hasSeriesMateIn2() { move legal : { not check imagine sidetomove reverse : { move legal : mate } } } ``` ### `import` ``` import "patterns.cql" ``` A CQLi extension. The named file's text is spliced in before parsing, so its definitions are in scope from that point. An imported file needs nothing but the definitions — no `cql` header, no trailing filter. Search order: the directory of the importing file, then each `--importpath` directory, then the semicolon-separated `CQL_IMPORT_PATH`. A name with no extension is retried with `.cql`. The current working directory is *not* searched (except for `--cql` text, which has no directory of its own). A file is imported at most once per query no matter how many times it is named, so two libraries may both import a third. Diagnostics name the imported file and its own line numbers. `import` is rejected under `--secure`. --- ## 21. TRANSFORM FILTERS A transform filter takes one target filter, optionally preceded by `count`, and evaluates several rewritten copies of it. ``` flip flipcolor fliphorizontal flipvertical reversecolor rotate45 rotate90 shift shifthorizontal shiftvertical [restrict] notransform currenttransform ``` ### What each one rewrites | Filter | Identity | Dihedral | Translation | Colour | 45° | |---|---|---|---|---|---| | `flip` | ✓ | ✓ | | | | | `flipcolor` | ✓ | ✓ | | ✓ | | | `fliphorizontal` | ✓ | ✓ | | | | | `flipvertical` | ✓ | ✓ | | | | | `reversecolor` | | ✓ | | ✓ | | | `rotate45` | ✓ | | | | ✓ | | `rotate90` | ✓ | ✓ | | | | | `shift` | ✓ | | ✓ | | | | `shifthorizontal` | ✓ | | ✓ | | | | `shiftvertical` | ✓ | | ✓ | | | - **Identity** is the untransformed original. Every transform filter except `reversecolor` includes it — which is why `flipcolor X` finds X *and* its color-reversed form, while `reversecolor X` finds only the reversed form. - **Dihedral**: the eight rotations/reflections of the board, applied to *square designators* and *directions*. - **Translation**: `shift*` moves square designators; 15 horizontal and 15 vertical offsets are possible. - **Colour**: swaps `black`↔`white`, `wtm`↔`btm`, `result 1-0`↔`result 0-1`, `elo white`↔`elo black`, `player white`↔`player black`; reverses piece colors inside piece designators and `fen` strings; and **also reflects square designators about the horizontal bisector**. - **45°**: `rotate45` rewrites directions only. - `light` and `dark` are swapped by any transform that changes a designated square's color. - `rank`, `file`, and `makesquare` are **never** affected by transforms. ### Result Each non-elided transformation (including identity) is evaluated in unspecified order. - With `count`: the number of transformations that matched. - Target is *Set*: the **union** of the matching transformations' results. - Target is *Numeric*: the **largest** matching value. - Otherwise: matches if any transformation matched. ### The rule people get wrong **Dihedral and shift transforms rewrite square designators and directions. They do not rewrite piece designators, set variables, or any other *Set* filter.** ```cql flip up 2 right 1 N // correct: all knight-attacked squares flip up 2 right 1 d5 // different! d5 gets rotated too ``` The first expands to eight direction pairs applied to `N`; the second also moves the square, giving a different set. `flipcolor`, by contrast, *does* rewrite piece designators (that is its purpose) and reflects square designators. A transform that produces no non-identity variants raises the warning "Superfluous transform does not modify any filters in the target filter" — for example `shift K`, since shifts do not touch piece designators. ### Nesting and order Nested transforms apply the innermost first, and dihedral reflections do not commute, so `shiftvertical rotate90 a1` and `rotate90 shiftvertical a1` differ. Duplicate transformations are elided: `rotate90 a1` and `flip a1` both give the four corners. ### Elision and `restrict` in shifts A translation that would empty a non-empty square designator is dropped: `shiftvertical a1` gives the whole a-file (downward shifts are elided). You can exploit this with sentinel squares: ```cql shifthorizontal { Pe5 Ke6 ke8 } // includes the drawn a- and h-file cases shifthorizontal { d1 f1 Pe5 Ke6 ke8 } // d1/f1 go empty at the edges, eliding them ``` When a designator names several squares, elision happens only if **all** of them would leave the board. `restrict` makes elision happen if **any** of them would: ``` shiftvertical [a1,h7] → the whole a and h files shiftvertical restrict [a1,h7] → [a1,h7] | [a2,h8] ``` ### `notransform` and `currenttransform` `notransform `*filter* blocks the effect of **enclosing** transforms on its target; transforms written inside the target still apply. It is the standard defence inside a function body: ```cql rotate90 notransform flipcolor { P & vertical P } // still "same file", not "same rank" ``` Written exactly like that, CQLi also warns "Superfluous transform", because `notransform` leaves `rotate90` with nothing at all to rewrite. In a real query the `rotate90` would wrap more than just the protected part. `currenttransform` is a *String* naming the transforms in effect (`identity`, `invertcolor`, `clockwise90`, `rotate180`, `counterclockwise90`, `reflect_h`, `reflect_v`, `reflect_a1h8`, `reflect_a8h1`, `up^n`, `down^n`, `left^n`, `right^n`), space-separated in application order; empty when none are in effect. `message currenttransform` inside a transform target is the way to see what is actually being evaluated. ### Idiom `flipcolor` is by far the most used transform: it turns a one-color pattern into a both-colors pattern. Anchor the side to move inside it so the two halves stay consistent: ```cql flipcolor { wtm mate power A - power a >= 10 } == { wtm mate power A - power a >= 10 } or { btm mate power a - power A >= 10 } ``` --- ## 22. PIECE IDENTITY AND IMAGINARY POSITIONS ### Piece tracking Every piece in the initial position gets a numeric **piece ID** (1, 2, 3, …, in FEN placement order). IDs follow pieces as they move and **survive promotion**, so a promoted queen can be traced back to its pawn. ``` piece $p = Set // piece variable: identity of the piece on that square ``` If *Set* is not exactly one occupied square, the variable is `None`. A piece variable converts automatically to a *Set* (its current square, or `[]` if captured) everywhere except as an argument to a user-defined function or to `str`, `comment`, `message`, or `pieceid`. In `str`/`comment`/`message` it prints as `Ke1` or `[absent]`. `pieceid` takes a piece variable or a one-square *Set* and yields the numeric ID (for a piece variable, even after capture); `None` for an empty or multi-square set. ```cql initial piece $p = Ra1 (flip count find $p & a1) == 4 // that rook visited all four corners echo (source target) { // two positions differing by a swap source < target source & target == . $swapped = square $sq in [Aa] { source:pieceid $sq != pieceid $sq } $swapped > 0 comment("squares of swapped pieces: " $swapped) } ``` ### Speculative `move` Appending `:` *filter* to a `move` filter makes it **speculative**: for each matching move, CQLi makes the move on a temporary copy of the board and evaluates *filter* at the resulting imaginary position. ```cql move legal count : mate // number of legal moves that mate move legal : mate // some legal move mates move from N to ~(. attackedby a) : check // a knight can check from a safe square ``` The result has the same type as the corresponding plain `move` filter but describes the moves whose target filter matched. The extra parameter `all` makes it *Boolean* `true` when the target matched for **every** move considered; `count all` gives the move count if every one matched, else `None`. Modes may be ordinary, `legal`, `pseudolegal`, or `reverse`. `previous` may not be speculative — use `parent : `*filter* instead. An ordinary speculative `move` considers no moves when evaluated at an imaginary position. ```cql // a mate in one was missed not mate parent : move legal : mate // ... and the side that missed it went on to lose not mate parent : { move legal : { mate flipcolor { wtm result 1-0 } } } ``` ### `imagine` ``` imagine imagine-specifier [imagine-specifier ...] : filter ``` Specifiers: ``` piece piece-designator --> Set place that piece on every square of Set (use _ to remove pieces); new piece IDs swap square1 square2 swap the two pieces (IDs preserved); if one square is empty the piece moves there sidetomove {black | white | reverse} ``` If a placement set is empty, or a swap operand is not exactly one square, or both swap squares are empty, that specifier does nothing. ``` imagine piece _ --> [Qq] swap K k : ... remove all queens, swap the kings imagine sidetomove reverse : move legal : mate ``` ### How imaginary positions behave They change **board state** but do not create game-tree nodes. - **Game-tree filters see the underlying real position**: `depth`, `distance`, `variation`, `mainline`, `ancestor`, `descendant`, `lca`, `child`, `parent`, `terminal`, `virtualmainline`, and *Position* comparisons. - **Board filters see the imaginary position**: `ply`, `movenumber`, `btm`, `wtm`, `attackedby`, `attacks`, `check`, `connectedpawns`, `fen`, `isolatedpawns`, piece designators, `mate`, `move`, `passedpawns`, `piece`, `square`, `pin`, `power`, `ray`, `zobristkey`. - `comment` in an imaginary position attaches to the underlying real position; `originalcomment` reads the real position's comment. Imaginary positions never appear in the output PGN. `saveposition` keeps an imaginary position alive until the end of the game and yields it as a *Position*, revisitable with `:`. Saved positions get their own position IDs but are never visited by the main loop, `find`, `echo`, `move`, or `consecutivemoves`. At a real position `saveposition` just yields the current position. `currentmutation` is a *String* describing the mutations in force: normal moves as SAN (`1.Bf4`); reverse moves as `1.~Bg5x(Q)c1`; placements as `` or ``; swaps as ``; side-to-move changes as `<&wtm>` / `<&btm>`. Empty when there are no mutations. A `saveposition` result is a real position and reports no mutations — call `currentmutation` on the imaginary position, not the saved one. ### `legalposition` and `reachableposition` Both are *Boolean* static analyses of the current board state alone (no history). `legalposition` requires: exactly one king of each color; at most 8 pawns per side; no pawns on rank 1 or 8; at most 16 pieces per side; the side **not** to move is not in check; White to move if this is the starting position; and the side to move's king attacked at most twice. `reachableposition` is a much deeper heuristic analysis of whether the position could arise in a real game of standard chess. It is the basis of retrograde puzzle work, and it will flag composed studies whose starting position is unreachable. As a top-level unparenthesised argument to `str`/`message`/`comment`, either filter yields an explanation rather than `true`/`false` — `` or ``. Both are especially useful after `imagine` or with reverse moves, since those readily produce impossible positions: ```cql move legal reverse count : reachableposition // reverse moves that make sense ``` --- ## 23. COMMENTS, MESSAGES, ASSERTIONS ### `comment` `comment(`*args*`)` behaves like `str` but attaches the resulting text to the current position in the output PGN. Always yields `true`. Parentheses are optional for a single argument. A position may carry several comments. ### `originalcomment` (alias `hascomment`, deprecated) - Bare: *String* — the original PGN comment(s) at this position, or `None`. Multiple original comments are joined with newlines. It always reflects the **input** PGN, unaffected by `comment` or `removecomment`. - Followed by a string literal that is a NAG (`"$1"`) or a recognized annotation symbol (`!`, `?`, `!!`, `??`, `!?`, `?!`, `=`, `+/=`, …): *Boolean*, whether that annotation is present. - Followed by any other string literal: *Boolean*, whether that text appears in any original comment at the position. Because comments are newline-joined and the regex `m` flag is on by default, `originalcomment ~~ "^123$"` matches when *one of* several comments is exactly `123`. To match across comments, use `"(.|\n)"` or `"(?s:.)"`. ### `removecomment` Removes the original comments at the current position from the output. Original text remains readable via `originalcomment`. ### `message` and `assert` `message(`*args*`)` prints to the console instead of the PGN, always yields `true`, and — unlike `comment` — **is never suppressed by Smart Comments or by the comment-suppression options**. Output is prefixed with game number and move unless `message quiet` is used. Messages are emitted asynchronously by default; `--noasyncmessages` serializes them. `assert `*condition* yields `true` when the condition matches, and otherwise prints a diagnostic (with board diagram and FEN) and **terminates the run**. To include values in the failure output, exploit short-circuiting: ``` assert $x <= 100 or { message("x = " $x) false } ``` ### Comments CQLi adds by itself | Kind | Where | Suppressed by | |---|---|---| | header (`Game number N`) | initial position of each matching game | `--noheader`, `--silent` | | match (`CQL`) | every matching position | `--matchstring ""`, `--quiet`, `--silent`, header `quiet`/`silent` | | sort (`desc: value`) | initial position | `sort quiet`, `--silent` | | auxiliary | from `find`, `echo`, `line`, `consecutivemoves` | those filters' `quiet` parameter, `--quiet`, `--silent` | | position ID (`id:N`) | variation positions referenced by a comment/message | as auxiliary | | user | from `comment` | `--silent` | Order at a position: header, match, sort, then user and auxiliary in the order added; original PGN comments come first. Duplicate comment text at one position is removed by default (`--nouniquecomments` keeps duplicates), and `--coalescecomments` merges a position's comments into one pair of braces. ### Smart Comments Comments added by `comment`, `line`, `find` and friends are **suppressed** when: - the position ultimately does not match the query; - a later filter in the same compound expression fails; - an enclosing filter fails, even if the query as a whole matches; - they belong to an evaluation that was not the best value of an enclosing `sort` (or a `line`'s selected sequence). This is what makes `sort ... { find all ... }` produce comments only for the piece/square/sequence that actually won. Disable with `--nosmartcomments` (alias `--alwayscomment`). **`settag`, `removetag`, and `message` do not participate in Smart Comments.** Once evaluated, their effects stand even if the position later fails to match. --- ## 24. CHESS VARIANTS Variants are recognized from the PGN `Variant` tag (aliases are case-insensitive; add more with `--variantalias`; skip unknown ones with `--skipunknownvariants`). | Variant | Recognised aliases | |---|---| | Atomic | `Atom`, `Atomic`, `Atomic Chess` | | Chess960 | `Chess960`, `Fischerandom` | | Crazyhouse | `Crazyhouse`, `Crazy House`, `House`, `ZH` | | Giveaway | `Antichess`, `Giveaway`, `Give away`, `Giveaway Chess` | | Horde | `Horde`, `Horde Chess` | | King of the Hill | `King of the Hill`, `kingOfTheHill`, `KOTH` | | Losers | `Losers` | | Racing Kings | `Race`, `Racing`, `RacingKings`, `Racing Kings` | | Standard | `Chess`, `Classical`, `Normal`, `Standard` | | Suicide | `Suicide`, `Suicide Chess` | | Three-Check | `Three-Check`, `Three Check`, `ThreeCheck`, `3-Check`, `3 Check` | Chess960 castling semantics (X-FEN) are supported for every variant, including Standard. Filters: - `variant` — *Boolean*, true for a non-standard variant. As an argument to `str`/`comment`/`message` it yields the canonical variant name instead (`Standard`, `Atomic`, …, or `unknown`); wrap it in parentheses to get the Boolean there. - `variantwin`, `variantloss`, `variantdraw`, `variantend` — *Boolean*, whether the game is won/lost/drawn/over by a variant-specific condition. `check`, `mate`, `stalemate`, `move`, and `pin` all adapt to variant rules (some variants allow promotion to a king, which is not subject to check; Giveaway has compulsory captures and no royal king; Atomic explodes; and so on). Reverse move generation always assumes standard chess. --- ## 25. RUN-LEVEL AND EXTERNAL FEATURES ### `cqlbegin` and `cqlend` ``` cqlbegin { ... } run once before any game is processed cqlend { ... } run once after all games are processed ``` Under multiple threads, each thread runs `cqlbegin` before its first game; `cqlend` runs once after persistent variables and dictionaries have been merged, which makes it the right place for a summary report. Multiple blocks run in the order written. Inside these blocks there is no real game: the current position is the standard starting position, `gamenumber` is `0` (a value it never has elsewhere), `result "*"` is `true`, and the other metadata filters yield `None`. ### `readfile` and `writefile` - `readfile `*filename* → *String* contents. A missing or unreadable file is a fatal runtime error. Relative names resolve against the current working directory; `CL_PATH` is not searched. - `writefile(`*filename string*`)` writes to a file; the CQLi-specific `noclobber` parameter appends instead of truncating. Both are rejected under `--secure`. The `commandpipe` filter (a CQLi extension) sends a request to an external program and returns its response, for engine evaluations, database lookups, and the like. A common shape — load a lookup table once, then use it per game: ```cql cql(silent) dictionary (min) $eco_dict function loadEcoFile($filename) { while ((readfile $filename) ~~ "^(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*)$") { $eco = \1 $fen = \5 $fen ~~ "^(.*? .*? .*?) " $eco_dict[\1] = $eco } } cqlbegin { loadEcoFile("chess-openings/dist/a.tsv") } ``` ### `hhdb` The `hhdb` filter is a purpose-built interface to the HHdbVI and HHdbVII endgame study databases, exposing their encoded composer, award, theme, and soundness information without parsing tags and comments by hand. Use `--vi` / `--vii` to select those input files. Consult the manual chapter "HHdbVI and HHdbVII Database Interface" before writing an `hhdb` query; do not guess its parameters. --- ## 26. FILTER INDEX ### Symbol filters | Filter | Type | Meaning | |---|---|---| | `.` | Set | all 64 squares | | `_` | Set | unoccupied squares | | `[]` | Set | no squares | | `` ` `` | any | identity operator | | `:` | any | with-position: evaluate RHS at LHS position | | `-` | Numeric | negation / subtraction | | `+` | Num/Str | addition / concatenation | | `*` `/` `%` | Numeric | multiply, divide, remainder | | `~` | Set | complement | | `&` | Set | intersection; positional intersection for two Positions | | `\|` | Set | union | | `#` | Numeric | cardinality of Set, String, or Dictionary | | `==` `!=` `<` `<=` `>` `>=` | varies | comparison (see section 4) | | `=` | Boolean | assignment | | `=?` | Boolean | conditional set assignment | | `+=` `-=` `*=` `/=` `%=` `&=` `\|=` | Boolean | compound assignment | | `[]` | varies | string slicing / dictionary access | | `[]=` | Boolean | slice or dictionary assignment | | `~~` | String | regex match and extract | | `\`*i* / `\-`*i* | Str/Num | capture group text / start index | | `\{`*n*`}` / `\-{`*n*`}` | Str/Num | named capture group text / index | | `\n` `\r` `\t` `\"` `\\` | String | predefined character literals | | `()` `{}` | varies | grouping | ### Named filters | Filter | Type | Meaning | |---|---|---| | `abs` | Numeric | absolute value | | `ancestor` | Boolean | ancestry test (deprecated; use `<`) | | `and` | Boolean | logical and | | `anydirection` | Set | rays in any direction | | `ascii` | Num/Str | character ↔ ordinal | | `assert` | Boolean | abort if the condition fails | | `attackedby` | Set | squares of LHS attacked by RHS | | `attacks` | Set | squares of LHS attacking RHS | | `between` | Set | squares between two sets | | `black` | Numeric | `-1` | | `btm` | Boolean | Black to move | | `check` | Boolean | side to move is in check | | `child` | Position | child position (`child(n)` for the nth) | | `colortype` | Numeric | signed piece type on a square | | `commandpipe` | String | request/response with an external program | | `comment` | Boolean | annotate the current position | | `connectedpawns` | Set | connected pawns, both colors | | `consecutivemoves` | Numeric | longest shared move sequence between two lines | | `cqlbegin` / `cqlend` | Boolean | run a block before/after all games | | `currentfen` | String | FEN with clock/move number normalized | | `currentmutation` | String | mutations of an imaginary position | | `currentposition` (`curpos`) | Position | the current position | | `currenttransform` | String | transforms in effect | | `dark` / `light` | Set | dark / light squares of a set | | `date` | String | `Date` (else `UTCDate`) tag | | `depth` | Numeric | variation depth | | `descendant` | Boolean | descendancy test (deprecated; use `>`) | | `diagonal` | Set | diagonal rays | | `dictionary` | Boolean | declare a dictionary | | `distance` | Numeric | moves between two positions via their LCA | | `doubledpawns` | Set | doubled pawns, both colors | | `down` | Set | downward rays | | `echo` | Bool/Num | relate the current position to every other | | `eco` | String | `ECO` tag | | `elo` | Numeric | player rating | | `event` / `eventdate` | String | `Event` / `EventDate` tag | | `false` / `true` | Boolean | constants | | `fen` | Bool/Str | match a placement string, or yield the current FEN | | `file` / `rank` | Numeric | 1–8 for a one-square set | | `find` | Pos/Num | search descendants (or `<--` ancestors) | | `flip` | varies | all 8 dihedral transforms | | `flipcolor` | varies | identity + color reversal | | `fliphorizontal` / `flipvertical` | varies | reflection transforms | | `function` | Boolean | define a function | | `gamenumber` | Numeric | index of the game in the input file | | `halfmoveclock` | Numeric | plies since capture or pawn move | | `hascomment` | String | deprecated alias for `originalcomment` | | `hhdb` | varies | HHdbVI / HHdbVII interface | | `horizontal` | Set | horizontal rays | | `if` | any | conditional | | `imagine` | any | evaluate on a modified board | | `import` | — | splice in another query file | | `in` | Boolean | subset / substring test | | `indexof` | Numeric | substring index | | `initial` | Boolean | this is the initial position | | `initialposition` | Position | position 0 | | `int` | Numeric | string → integer | | `isbound` / `isunbound` | Boolean | is a variable bound | | `isolatedpawns` | Set | isolated pawns, both colors | | `key` / `string` | Boolean | iterate dictionary keys | | `lca` | Position | latest common ancestor | | `left` / `right` | Set | leftward / rightward rays | | `legalposition` | Boolean | board state is legal | | `line` | Num/Pos | match a sequence of positions | | `loop` | Boolean | repeat while the body matches | | `lowercase` / `uppercase` | String | case conversion | | `maindiagonal` / `offdiagonal` | Set | NE-SW / NW-SE rays | | `mainline` | Boolean | position is on the mainline | | `makesquare` | Set | square from a string or file/rank pair | | `mate` | Boolean | side to move is checkmated | | `max` / `min` | Num/Str | largest / smallest of several values | | `message` | Boolean | print a diagnostic message | | `move` | Bool/Set/Num | inspect played, legal, or hypothetical moves | | `movenumber` | Numeric | current move number | | `northeast` / `northwest` / `southeast` / `southwest` | Set | diagonal rays | | `not` | Boolean | logical negation | | `notransform` | any | block enclosing transforms | | `or` | Boolean | logical or | | `originalcomment` | String | original PGN comment at this position | | `orthogonal` / `vertical` | Set | orthogonal / vertical rays | | `parent` | Position | parent position | | `passedpawns` | Set | passed pawns, both colors | | `persistent` | Boolean | declare a cross-game variable | | `piece` | Bool/Set | declare a piece variable, or iterate pieces | | `pieceid` | Numeric | numeric identity of a piece | | `pin` | Bool/Set | find pins | | `player` | String | `White` / `Black` tag | | `ply` | Numeric | half-moves since the initial position | | `position` | Position | position by ID | | `positionid` | Numeric | ID of the current position | | `power` | Numeric | material value of a set of squares | | `promotedpieces` | Set | squares holding promoted pieces | | `ray` | Set | pieces arranged along a ray | | `reachableposition` | Boolean | position could arise in a real game | | `readfile` | String | contents of a file | | `removecomment` | Boolean | drop original comments here | | `removetag` | Boolean | drop a PGN tag | | `replace` | String | regex replacement | | `result` | Boolean | game termination token | | `reversecolor` | varies | color reversal without identity | | `rotate45` | varies | rotate directions by 45° | | `rotate90` | varies | rotate the board by 90° | | `saveposition` | Position | persist an imaginary position | | `settag` | Boolean | set a PGN tag | | `shift` / `shifthorizontal` / `shiftvertical` | varies | translate square designators | | `sidetomove` | Numeric | `white` or `black` | | `site` | String | `Site` tag | | `sqrt` | Numeric | integer square root | | `square` | Bool/Set | iterate over squares | | `stalemate` | Boolean | side to move is stalemated | | `standardfen` | String | full FEN | | `str` | String | stringify and concatenate | | `tag` | String | value of a named PGN tag | | `terminal` | Boolean | position has no children | | `type` | Numeric | unsigned piece type on a square | | `unbind` | Boolean | make a variable `None` / delete a key | | `up` | Set | upward rays | | `variant` | Boolean | game is a non-standard variant | | `variantdraw` / `variantend` / `variantloss` / `variantwin` | Boolean | variant-specific outcomes | | `variation` | Boolean | position is in a variation | | `virtualmainline` | Boolean | position is virtual-mainline | | `while` | Boolean | loop while a condition matches | | `white` | Numeric | `1` | | `writefile` | Boolean | write a string to a file | | `wtm` | Boolean | White to move | | `xray` | Set | x-rays through intervening pieces | | `year` | Numeric | year from `Date` / `UTCDate` | | `zobristkey` | String | Zobrist hash of the position, in hex | --- ## 27. KEYWORDS Reserved. A variable or function may not use any of these names. ``` abs all ancestor and anydirection ascii assert attacked attackedby attacks between black btm by capture castle check child colortype commandpipe comment connectedpawns consecutivemoves count cql cqlbegin cqlend curpos currentfen currentmutation currentposition currenttransform dark date depth descendant diagonal dictionary distance doubledpawns down drop echo eco elo else enpassant enpassantsquare event eventdate false fen file find firstmatch flip flipcolor fliphorizontal flipvertical focus from function gamenumber halfmoveclock hascomment hhdb horizontal if imagine import in indexof initial initialposition input int isbound isolatedpawns isunbound keepallbest key lastposition lca left legal legalposition light line loop lowercase maindiagonal mainline makesquare matchcount matchstring mate max message min move movenumber nestban noclobber nolinearize nonatomic northeast northwest not notransform null offdiagonal or originalcomment orthogonal output parent passedpawns persistent piece pieceid piecepath pin place player ply position positionid power previous primary promote promotedpieces pseudolegal quiet rank ray reachableposition readfile removecomment removetag replace restrict result reverse reversecolor right rotate45 rotate90 saveposition secondary set settag shift shifthorizontal shiftvertical sidetomove silent singlecolor site sort southeast southwest sqrt square stalemate standardfen str string sum swap tag terminal then through title to true type unbind up uppercase variant variantdraw variantend variantloss variantwin variation variations verbose vertical virtualmainline while white writefile wtm xray year zobristkey ``` --- ## 28. PRECEDENCE Lower number binds tighter. Everything is left-to-right associative except `:` and the relational operators, which are right-to-left. Parentheses and braces override precedence at no runtime cost. | Level | Filters | |---|---| | 1 | unary `-`, `` ` ``, `tag` | | 2 | `:` (right-to-left) | | 3 | `[]` (slicing/dictionary; takes the LHS's precedence) | | 4 | `~~` (takes the LHS's precedence) | | 5 | `dark`, `light`, all direction filters | | 6 | `move` (`from`, `to`, `capture`, `enpassant`, `:` parameters), `pin` (`from`, `to`, `through`) | | 7 | `attacks`, `attackedby` | | 8 | `~` | | 9 | `&` | | 10 | `\|` | | 11 | `between` args, `colortype`, `file`, the `in` parameter of `piece`/`square`/`string`, `lowercase`, `piece` assignment, `pieceid`, `power`, `rank`, `type`, `uppercase` | | 12 | `/` `*` `%` | | 13 | `+` `-` | | 14 | `#`, `int`, `readfile` | | 15 | `abs`, `ascii`, `makesquare`, `position`, `sqrt`, `[]=`, `/=` `*=` `%=` `+=` `-=` `&=` `\|=` `=?` | | 16 | `<` `<=` `==` `>` `>=` `!=` (right-to-left) | | 17 | `in` (the membership filter, not the parameter) | | 18 | `not` | | 19 | `and` | | 20 | `or` | | 21 | body of `find`, RHS of `=`, bodies of transform filters | | 22 | `{}` constituents; `assert`, `comment`, `echo` body, `if`/`then`/`else`, `imagine`, `line` constituents, `message`, `persistent`, `sort` body, `str`, `while` condition; iterator bodies of `loop`/`piece`/`square`/`string`/`while`; user-defined function arguments; the parenthesised arguments of `ancestor`, `child`, `consecutivemoves`, `descendant`, `distance`, `indexof`, `lca`, `makesquare`, `min`, `max`, `ray`, `settag`, `writefile`, `xray` | Two consequences worth internalising: - `attacks`/`attackedby` bind tighter than `&`, `|`, `~`, `#`, and comparisons, so `#A attacks $sq > a attacks $sq` already means `#(A attacks $sq) > #(a attacks $sq)`. - `power A - power a >= 10` parses as intended, because `power` (level 11) binds tighter than `-` (13) which binds tighter than `>=` (16). ### Precedence vitiation When strict precedence would give a binary infix filter a left operand of the wrong type, CQLi widens the left operand to the next grammar production until the type fits, or reports a syntax error. So `# str a1 * 5` parses as `#(str a1) * 5`, not `#(str (a1 * 5))`, because `a1` and `str a1` are not *Numeric*. This is what lets `position 0 : wtm` mean `(position 0) : wtm`. It is also how `int "24" + 1` and `int "24" + "1"` both work. Filters that participate, with the left-operand type they need: `attackedby` (*Set*), `attacks` (*Set*), `in` (*Set*/*String*), `|` `&` (*Set*), `~~` (*String*), `[]` slicing (*String*), `+` (*String*/*Numeric*), `-` `*` `/` `%` (*Numeric*), `:` (*Position*), `!=` `==` `<` `<=` `>` `>=` (*Numeric*/*Set*/*String*/*Position*). Run with `-w 3` to see an info message when this happens, or `--parse` to see the resulting tree. When in doubt, parenthesise. ### Order of evaluation Precedence fixes grouping but **not** evaluation order. Unless a filter's description says otherwise, the order in which operands are evaluated is unspecified — which matters only for filters with side effects (assignments, `comment`, `message`, `writefile`). Do not combine several side-effecting filters in one expression and depend on their order. --- ## 29. WORKED EXAMPLES All of these come from the CQLi manual. Use them as patterns. ### Getting started ```cql mate // games containing checkmate btm mate // White delivered mate mate or stalemate // either check not mate // a check that is not mate mate Qh7 kg8 // mate with white queen h7, black king g8 k attackedby Q // the black king is attacked by a white queen terminal movenumber < 20 // the game ended before move 20 2000 <= year <= 2005 // played 2000-2005 ``` ### Material ```cql // mate delivered while at least a rook and bishop down btm mate power a - power A >= 8 // the same for either side flipcolor { wtm mate power A - power a >= 10 } // exactly three pieces on the board: K, Q vs k, and stalemate stalemate [Aa] == 3 flipcolor { K Q k } // mate in a KNB vs K ending terminal mate flipcolor { A == K a == 3 n == 1 b == 1 } ``` ### Attacks and defence ```cql // squares White attacks . attackedby A // undefended black pieces attacked by White a attackedby A & ~ a attackedby a // squares on Black's half attacked by White and not defended a-h5-8 & (. attackedby A & ~ . attackedby a) // underdefended black pieces square $sq in a { a attacks $sq #A attacks $sq > a attacks $sq } // every square attacked by exactly one side . attackedby A & . attackedby a == [] . attackedby A | . attackedby a == 64 // a pawn forking two or more pieces flipcolor [bnrqk] attackedby P > 1 // ... by one particular pawn (the fork is real, not spread across pawns) flipcolor piece $Pawn in P { [bnrqk] attackedby $Pawn > 1 } ``` ### Mating patterns ```cql // mate by discovered double check mate flipcolor { wtm a attacks K > 1 } // smothered mate: every square around the king is blocked by its own men mate flipcolor { wtm [_a] attackedby K == [] } // a king in the middle of the board with all eight neighbours empty flipcolor { _ attackedby K == 8 } // mate answered a check mate parent : check ``` ### Pins, rays, x-rays ```cql ray (A a A a) // four men in a line, alternating color ray diagonal (B n k) // white bishop pinning a black knight ray up (R [Pp] r) // rooks on a file separated by one pawn ray orthogonal (R R q k) // rook battery pinning the queen to the king flipcolor xray(B r q) // bishop x-rays a rook onto a queen pin // pieces pinned to their own king pin from [Aa] // the pinning pieces, same pins pin to . // every pin, including onto empty squares // bishop x-rays the king onto a heavy piece, and the king must move flipcolor { xray(B k [qr]) move from k to _ } ``` ### Pawn structure ```cql passedpawns & p // black passed pawns doubledpawns & a-d1-8 // queenside doubled pawns connectedpawns & passedpawns // connected passed pawns flipcolor Pa-h5-8 & passedpawns // advanced passed pawns, either color flipcolor p & up 1 P // fixed (head-to-head blocked) pawns flipcolor P & diagonal 1 P // pawn chains (flipcolor P & diagonal 1 P) & (flipcolor P & ~ up horizontal 0 1 P) // the bases of pawn chains shifthorizontal flipcolor { $TP = down a8 & P $TP > 2 $TP } // tripled pawns ``` ### Castling, promotion, en passant ```cql // both sides castled at some point (either kingside or queenside) initial find move o-o find move o-o-o // castling into check delivered by the opponent's reply check move previous castle // neither side castled all game terminal not find <-- move castle // an underpromotion move promote [BNR] // promotion to a queen that stalemates stalemate move previous promote Q // a knight underpromotion that immediately forks heavy pieces flipcolor { btm piece $pid = move to . previous promote N [krq] attackedby $pid > 1 } // both sides have a promoted queen on the board promotedpieces & Q promotedpieces & q // two en passant captures within 10 plies of each other move previous enpassant echo ($first $second) { move previous enpassant $first < $second distance($first $second) <= 10 } ``` ### Hypothetical moves ```cql // more than five different legal moves deliver mate move count legal : mate > 5 // an en passant capture would be a discovered check move legal enpassant : { check flipcolor { [brq] attacks K } } // a knight has a move that forks king and queen flipcolor piece $knight in N { move from $knight legal : { [kq] attackedby $knight > 1 } } // a mate in one was available and missed not mate parent : move legal : mate // ... and the player who missed it went on to lose not mate parent : { move legal : { mate flipcolor { wtm 1-0 } } } // castling that walks into mate, where passing would have mated instead move castle child : not move legal : mate imagine sidetomove reverse : move legal : mate ``` ### Sequences with `line` ```cql // check, resolved by capturing the checking piece, then mate line --> check --> move previous capture (flipcolor A attacks k) --> mate // at least three check-then-capture pairs immediately before mate line --> (check --> move previous capture (flipcolor A attacks k)) {3,} --> mate // one side gave ten or more checks in a row line singlecolor --> check {10,} // White was checked three or more times in a row line focus wtm --> check {3,} // runs of five or more consecutive captures, without reporting sub-runs line nestban --> move capture . {5,} // the capture sequence that ends with the best material balance for White line max power A - power a --> move capture .{+} // longest run of checks in each game, sorted, minimum ten sort "Consecutive checks" { line singlecolor nestban --> check {+} } >= 10 ``` ### Whole-game questions ```cql // most captures made by a single piece; report the best one per game initial piece $p in [Aa] { sort "Captures by one piece" { find all move previous capture . from $p } >= 10 } // most captures on a single square initial square $sq in . { sort "Captures on one square" { find all move previous capture $sq } >= 10 } // more than five promotions within one line terminal sort "Number of promotions" find <-- all move promote A > 5 // long decisive games, longest first terminal flipcolor result 1-0 sort "Total moves" movenumber > 100 // a rook that visited all four corners initial piece $p = Ra1 (flip count find $p & a1) == 4 // games with between 100 and 200 terminal positions, sorted by that count cql(variations) 100 <= (sort find all terminal) <= 200 ``` ### Position relationships with `echo` ```cql // two positions identical except for whose turn it is echo ($source $target) { $source & $target == . sidetomove != $source:sidetomove } // two positions differing only in whether en passant is legal move legal enpassant echo ($source $target) { not move legal enpassant $source & $target == . sidetomove == $source:sidetomove $source:move legal enpassant: comment("enpassant capture " currentmutation " legal here") comment("enpassant capture not legal here") } // two positions differing only by pieces of one type having swapped places echo ($source $target) { $source < $target $source & $target == . $swapped = square $sq in [Aa] { $source:pieceid $sq != pieceid $sq } $swapped > 0 comment("squares of swapped pieces: " $swapped) } ``` ### Players and metadata ```cql // all Anand games, oldest first, no comments added cql(silent) initial player "Viswanathan" sort min tag "Date" // a win by the player rated at least 400 points lower initial flipcolor { result "1-0" elo white + 400 <= elo black } ``` ### Editing games rather than selecting them These use `cql(silent)` so that CQLi does not add its own markers. ```cql // set PlyCount to the mainline length cql(silent) terminal mainline settag("PlyCount" str ply) // record the final position cql(silent) terminal settag("EndFEN" standardfen) // fill in a missing seven-tag roster cql(silent) initial if not tag "Event" then settag("Event" "?") if not tag "Site" then settag("Site" "?") if not tag "Date" then settag("Date" "????.??.??") if not tag "Round" then settag("Round" "?") if not tag "White" then settag("White" "?") if not tag "Black" then settag("Black" "?") if not tag "Result" { $result = "*" if result "1-0" then $result = "1-0" if result "0-1" then $result = "0-1" if result "1/2-1/2" then $result = "1/2-1/2" settag("Result" $result) } // rename a tag cql(silent) initial settag("NewTagName" tag "OldTagName") removetag "OldTagName" // games whose Result tag disagrees with the termination token cql(silent) initial tag "Result" $expected = "*" if result 1-0 then $expected = "1-0" if result 0-1 then $expected = "0-1" if result 1/2-1/2 then $expected = "1/2-1/2" $expected != tag "Result" // strip clock annotations, keeping the rest of each comment cql(quiet) if hascomment { removecomment $newcomment = replace((originalcomment) "\[%clk .*?\]" "") if $newcomment != "" { comment $newcomment } } // remove every comment cql(quiet) removecomment // annotate every position with its Zobrist key cql(quiet) comment zobristkey ``` Output-side operations are command-line work, not query work: ``` cqli -i in.pgn -o 'output/%T{Event}.pgn' --silent split by tag cqli -i in.pgn -o 'output/%Q{str gamenumber % 10}.pgn' --silent split by query cqli -i in.pgn -o out.pgn --silent --elidenags drop NAGs cqli -i in.pgn -o out.pgn --silent --elidecomments drop comments cqli -i in.pgn -o out.pgn --silent --elidevariations drop variations cqli -i in.pgn -o out.pgn --silent --pgnlinewidth 0 one line of movetext ``` ### Geometry and curiosities ```cql flipcolor dark [Aa] == [Aa] // every piece on one color of square shifthorizontal up 0 7 a1 & [Aa] == 8 // a completely full file dark [Bb] // dark-squared bishops move to light . legal from R // light squares a white rook can reach ``` --- ## 30. COMMON MISTAKES Check your query against this list before you hand it over. **1. Confusing "yields a value" with "matches".** `0` and `""` match; `false`, `None`, and `[]` do not. `power A` always matches, even at `0`. Write the comparison you actually mean. **2. `A in B` is true when A is empty.** A piece variable becomes the empty set once that piece is captured, so `find $Pawn in a-h8` matches on capture as well as on promotion. Use `&`: `find $Pawn & a-h8`. **3. `=` versus `=?` for sets.** `$X = []` stores the empty set and the assignment matches; `$X =? []` leaves `$X` alone and yields `false`, failing the surrounding sequence. Note that `=?` failing does not mean `$X` changed — and `=?` succeeding does not mean it changed either. **4. Backslash sequences do not work inside string literals.** `"A\n"` is three characters. Use the filter: `"A" + \n`. There is no way to escape anything inside a CQL string literal. **5. Set-keyed dictionary subscripts are ambiguous.** `$Dx[a2]` parses as two filters. Write `$Dx[ a2 ]`, ``$Dx[`a2]``, or `$Dx[[a2]]`. **6. Reading a missing dictionary key does not create it, but `+=` does.** `$D[$k]` yields `None`; `$D[$k] += 1` autovivifies `$k` to `0` first. To touch only existing keys, guard with `if $D[$k] then ...`. **7. Forgetting that variations are off by default.** Any query about "all lines of the game" needs `cql(variations)` or `--variations`. **8. Writing a whole-game query without an anchor.** `find`, `echo`, and `line` run at *every* position unless you gate them with `initial`, `terminal`, or a cheap board test. Unanchored, they are slow and they annotate far more positions than intended. **9. `move` result type depends on parameter order.** `move from . previous` is a *Set*; `move previous from b4` is a *Boolean*. Only `from`/`to`/`capture` in **first** position produce a *Set*, and only when `count` is absent. **10. `move previous` evaluates its set arguments in the previous position.** `move previous from B` is right; thinking of `B` as the bishop's current square is wrong. **11. Forgetting `count`.** `move legal` is a *Boolean*. The number of legal moves is `move legal count`, and `mate` is `check and move legal count == 0`. **12. Assuming dihedral transforms rewrite piece designators.** They rewrite square designators and directions only. `flip up 2 right 1 N` is the knight pattern; `flip up 2 right 1 d5` also moves `d5`. `flipcolor` is the exception — it does rewrite piece colors, and it reflects square designators vertically. **13. Transform leakage into functions.** A function is inlined at the call site and inherits enclosing transforms. Wrap the body in `notransform` when the function encodes a fixed orientation (`vertical`, `up`, `Pa-h5-8`, …). **14. `*` and `+` in `line` constituents are read as arithmetic when they can be.** Use `{*}` and `{+}`. **15. Mixing `-->` and `<--` in one `line`.** Not allowed. Also, `nestban` does not work on backward lines. **16. Forgetting `nestban`.** Without it, a matching sequence's every tail matches too, flooding the output. **17. A greedily-parsed range swallowing the body.** `find 1 10 -3` is range `[1:1]`, body `10-3`. Brace the body. **18. Using `!` for negation.** The operator is `not`. `!=` exists, `!` does not. **19. Commas between function arguments.** Arguments are whitespace-separated: `XOR(A B)`, not `XOR(A, B)`. **20. Bare identifiers that are really piece designators.** `b` is black bishops; `p` is black pawns. Prefix every variable with `$`. **21. Expecting `!=` to behave like a comparison.** `X != Y` is `not X == Y`, so it is `true` when either side is `None` — including when both are. The other comparisons yield their left operand or `None` and never yield `false`. **22. Expecting `tag` to see your `settag`.** It never does; `tag` always reads the input PGN. And `settag`/`removetag`/`message` are exempt from Smart Comments, so their effects persist even when the position ends up not matching. **23. Using `result` when you meant the tag.** `result 1-0` reads the game termination token. `tag "Result"` reads the tag. They can disagree. **24. Assuming `distance` counts full moves.** It counts moves in the game-tree sense — one per ply. `distance($a $b) <= 10` means within ten half-moves. **25. Assuming even ply means White to move.** True unless the game has a `FEN` tag starting with Black to move. Use `wtm`/`btm`. **26. Recursion in functions.** Impossible; functions are inlined. Use `while` or `loop`. **27. Letting a `while` body's early failure skip the loop update.** If a filter in the body fails, later filters in the body are not evaluated — including your increment. Update the loop variable first. **28. Integer division.** `10 / 3` is `3`. There is no fractional type; scale to centipawns or decipawns if you need finer material values. --- ## 31. DIFFERENCES FROM CQL 6.1 If you have seen CQL before, these are the places where CQLi differs. Everything listed as an extension will **not** work in CQL 6.1. ### Present in CQLi, absent from CQL 6.1 - Variant support: `variant`, `variantwin`, `variantloss`, `variantdraw`, `variantend`, and the `move` parameter `drop`, plus correct parsing and move generation for Atomic, Chess960, Crazyhouse, Antichess/Giveaway, Horde, King of the Hill, Racing Kings, Three-Check. - Imaginary positions: `imagine`, the speculative `move` filter, `currentmutation`, `saveposition`. - Filters: `cqlbegin`, `cqlend`, `halfmoveclock`, the identity operator `` ` ``, `key`, `legalposition`, `reachableposition`, `promotedpieces`, `removetag`, `replace`, `standardfen`, `zobristkey`, `import`, `commandpipe`. - `line` parameters `focus`, `max`, `title`, `verbose`, `piecepath`, `keepallbest`, `nolinearize`, `nonatomic`, and `{?}` as an alias for `?`. - `move`: `reverse` move generation; combining `capture`/`promote` with `legal`/`pseudolegal`; `count` with every mode, not just `legal`/`pseudolegal`. - Dictionaries with *Numeric* and *Set* keys and values; conditional and compound assignment on dictionary members; merge strategies for multi-threaded runs. - `restrict` on shift transforms. - Full Unicode support: `#` on a string counts code points (CQL 6.1 counts bytes), slicing indexes code points, `ascii` covers 0–127, string comparison and `sort` use Unicode collation, regexes support named groups, lookbehind, possessive matching, in-pattern comments, and case-insensitive flags. - Extended Smart Comments and commit logging; block-scoped variables in loops and functions; multiple comments per position preserved. - `--limit`, `--warnlevel`, `--pgnlinewidth`, `--secure`, `--keepallbest`, `--nosort`, `--noclobber`, `--createdirectories`, `--showdictionaries`, `--noremovetag`, the comment/move/variation compaction options, and expressive diagnostics. ### Behavioural differences to be aware of - `cql()` headers are always optional in CQLi, may appear anywhere a function definition may, and only the last one counts. - `1-0`, `0-1`, `1/2-1/2` are single tokens in CQL 6.1 but not in CQLi, so `$x = 1-0` parses fine and `result 1 - 0` means `result 1-0`. - A range after two nested transform keywords applies to the composition in CQL 6.1 but to the innermost transform in CQLi: `shift flip count {a1 a7}` is `64` in CQL 6.1 and `8` in CQLi. - CQLi linearizes ordinary `move` filters inside backward (`<--`) `line` constituents; CQL 6.1 does not. Use `nolinearize` for the old behavior. - CQLi's `line` uses a non-backtracking NFA, so it cannot suffer catastrophic backtracking — but which of several equally-long sequences is reported may differ from CQL 6.1. - CQLi honours the en passant square, castling rights, and full-move number from a game's `FEN` tag; move numbers therefore start where the tag says. - `int` in CQLi skips leading whitespace and trailing non-digits; CQL 6.1 does not match such strings. - `isbound` resolves identifiers at parse time in CQLi, so it is `false` for a variable not yet in lexical scope; CQL 6.1 resolves at run time. - With `count` and `legal`/`pseudolegal`, CQLi counts promotions to different pieces separately; CQL 6.1 does not. The CQL 6.1 number is `move count legal - 3 * move count legal promote A / 4`. - `settag`'s tag argument need not be a literal in CQLi, and no tag names are off limits. - CQLi preserves semicolon (one-line) PGN comments, converting them to braces; CQL 6.1 discards them. CQLi also recognizes `pass` as a null move. - Neither PGN nor CQL files need a particular extension in CQLi. - `-gui`, `-guipgnstdin`, and `-guipgnstdout` are not supported. --- *End of file. Canonical documentation: https://cql64.com/manual/*