ccppccpp home
Browse the documentation

Plugins · CCPP PQL

Syntax

PQL tokens, grammar, keyword phrases and how the parser recovers from errors while you type.

The parser is hand-written and error-tolerant: it always returns a program with its diagnostics, so the prompt box can color and check a program while you type. Placement, types and scopes are checked after it.

Tokens

TokenForm
Word[A-Za-z_][A-Za-z0-9_]*, inner - allowed: keywords, connectors, identifiers, null, true, false
String"…" on one line. Escapes: \" \\ \n \t \r \{ \} \u{…}; any other is PQL-S004
Number-?digits(.digits)?; 250px is PQL-S018
Path@path/to/dir ends at whitespace or , ; { } ( ) [ ] "; quote any other: @"a b"
Policy!kebab-id
Call#kebab-id(
Interpolation{{ value.path }}, no expression (PQL-S009)
Comment// … to the end of the line, /* … */
Punctuation{ } ( ) [ ] , ; :

Grammar

program     = { statement } ;
statement   = scope | objective | stop | chain | how-block | directive
            | policy-ref | call | for-loop ;
scope       = ("FROM" | "MAKE") args ( ";" | block ) ;
objective   = ("ADD" | "REMOVE" | "MODIFY" | "REFACTOR" | "SKIP" | "CONTINUE") [ args ] ";" ;
stop        = ("SESSION SUCCESS" | "STOP WHEN" | "ASK WHEN") args ";" ;
chain       = branch { branch } ;
branch      = ("IF HAPPEN" | "IF NOT HAPPEN" | "ELSE IF HAPPEN" | "ELSE IF NOT HAPPEN" | "ELSE") [ args ] block ;
how-block   = "HOW" block ;
directive   = HEAD clause { CONNECTOR clause } ";" ;
clause      = [ group { "," group } ] ;
group       = value { value } ;
args        = value { "," value } ;
policy-ref  = POLICY ";" ;
call        = CALL "(" [ argument { "," argument } ] ")" ";" ;
argument    = [ WORD ":" ] value ;
for-loop    = "FOR" WORD "IN" ( "RANGE" "(" value "," value ")" | value ) block ;
block       = "{" { statement } "}" ;
value       = STRING | NUMBER | PATH | INTERPOLATION | WORD | "[" [ value { "," value } ] "]" ;

A directive's clauses split on the connectors of its form: LIMIT FILE TO 250 LINES has the clauses FILE and 250 LINES. The grammar is deliberately wider than the language (a block after ADD, a string where a path is expected) so that the validator reports the precise error instead of a generic one.

Keyword phrases match longest first: ELSE IF NOT HAPPEN before ELSE, then SESSION SUCCESS, STOP WHEN, ASK WHEN, DEPTH MAX, STRUCTURE BY, ERRORS USING, ASYNC USING, TEST USING, INTEGRATE VIA, SPLIT WHEN. An unknown first word is PQL-S008, with the closest keyword as a suggestion.

Header form

At the root, FROM @dir; takes every statement after it: FROM @a; ADD "x"; and FROM @a { ADD "x"; } are the same program. Inside a block the header form is refused (PQL-D002).

Error recovery

SituationRecovery
Missing ;Reported after the last token; a keyword starting a new line begins the next statement (PQL-S005).
Missing }The block ends at the end of the program (PQL-S006, pointing at its {).
Stray }Reported and skipped (PQL-S017).
Unknown keyword or tokenThe rest of the statement is skipped up to ; or its block.
Unterminated stringIt ends at the end of its line (PQL-S002).

Normalized form

ccpp pql fmt prints the canonical program: block form, one statement per line, two-space indentation, a blank line between root statements, strings re-escaped and paths quoted only when needed. Comments are dropped. ccpp pql fmt --check exits 1 when a file is not already formatted.

The compiled prompt ends with the same normalized form after expansion: each function call is printed expanded after a // #call(...) comment, so the normalized program compiles again to the same rules.