A filter is a CQL construct which can be either true or false of a given chess position.

By "position" here we don't just mean the board position, but also where that position occurs in the game, including all the preceding and subsequent positions.

A filter is only ever applied to whatever the current position is. But it can be applied to many different positions in a game as the current position is updated by the CQL main loop. Some filters, like next and previous can themselves temporarily modify the current position.

For example, one filter is the check filter. This will be true if one of the Kings is in check, and will be false otherwise. The name of the check filter is simply check

Many filters include other filters as constituents. For example, an or filter has two constituents, its first clause and its second clause. Thus,

check or Ra3
is a single filter with two constituents. The filter matches a position if and only if either one of the Kings is in check or if the White rook is on a3 (or both, of course).

Filters between braces

Braces introduce a kind of filter called a composite filter. A composite filter matches a position if and only each of its clauses match. For example,
{check Ra3}
is a single composite filter that matches a position in which both one of the Kings in in check and the white Rook is on a3.

The filters in the body of the CQL form are implicitly enclosed in a composite filter: they all must match in order for the body to match. The square and piece filters also take these implicit composite filters as arguments.

Parsing filter expressions

Sometimes it can seem difficult to parse an expression using filters. For example, what does this mean?
not Ra3 or check
Maybe it means, one might think,
{not Ra3} or check
Which would match a position where there is no white Rook on a3 or which is a check. Or maybe it means
not {Ra3 or check}
which would match a position in which it is not the case that there is either a Rook on a3 or it is check, that is, this would match positions that are not checks and in which there is no white Rook on a3.

CQL always uses the second interpretation. All operators in CQL - not, or, on - have equal precedence, and CQL always tries to make arguments as long as possible as soon as possible.

Thus, the filter not takes a single argument, a filter. not matches a position if and only if its argument does not match.

So, when parsing not Ra3 or check, CQL tries to make the argument to not as long as possible. Since

Ra3 or check
is longer than just
CQL will interpret not Ra3 or check as being the filter applied to the single argument,
Ra3 or check
Note that {Ra3 or check} is exactly the same as Ra3 or check. It is a composite filter with one argument, the or filter Ra3 or check.

reading argument lists

Sometimes reading CQL code can be confusing because an argument list might contain more than one filter. An argument list is a series of one or more filters separated by white space and enclosed in parentheses.

For example, the argument list

(Ra3 check)
is an argument list with two filters in it . Similarly
(Ra3 or Nb2 check or stalemate)
is another argument list with two filters. The first filter is the or filter
Ra3 or Nb2
which matches a position if there is a white rook on a3 or a white knight on b2. The second filter is
check or stalemate
which is true if either the position is stalemate or check

This could be written equivalently as

({Ra3 or Nb2} {check or stalemate})
If it's clearer to you, always enclose filters in braces.

For example, the next filter takes as its argument a list of filters. These filters must match successive future positions from the current position. Thus,

next (Ra3 or Nb2 check or stalemate)
is identical to
next ({Ra3 or Nb2}
      {check or stalemate}
This matches the current position if the current position has either a R on a3 or an N on b2, and if the following position - the position after the next move - is either a check or a stalemate.

You might be tempted to think that

(Ra3 or Nb2 check or stalemate)
Might be parsed as
(Ra3 or {Nb2 check} or stalemate)
but this would be incorrect, because
Nb2 check
is not the same as
{Nb2 check}

Note that there is no filter

Nb2 check
The rule to remember here is this: Two filters next to one other separated by white space is never a single filter

More parsing examples

{not Ra3 check}
matches positions where the R is not on a3 and the position is a check. It is equivalent to
{ {not Ra3} check}
This is because there is no such single filter
Ra3 check
so that
Ra3 check
cannot be an argument to not, which takes only a single filter as its argument.

This rule is most commonly applied to transform filters (shift, flip and so on). All of these take a single filter argument, but it is common to want to apply these to multiple filters at once. Remember to enclose their bodies in braces if you want to apply them to multiple filters at once. For instance, use

{rotate90 Ka1 kh8}
would be parsed as
{{rotate90 Ka1} kh8}
which would match a position with the white King on a corner and the black on h8.

By contrast,

{rotate90 {Ka1 kh8}}

would match positions with the kings on diagonally opposite corners.

However, since a transform filter is itself one filter, they can be chained:

flipcolor rotate90 {Ka1 kh8}
would apply flipcolor to the result of applying rotate90 to {K1 kh8} (which in point of fact would not change anything semantically here).

filters that take parameters as well as arguments

Some filters take parameters as well as an argument list. These parameters might be a range or other parameters that come between the name of the filter and its arguments.