Trainerflow Docs trainerflow.com
The language › Grammar

The language

Grammar

The complete syntax of Coach on one page, with the order in which operators apply.

This page is a reference. It describes the syntax of Coach as the compiler sees it, which is handy when you want to know exactly what is allowed where. If you are learning the language, the pages on values, the core elements and the commands explain the same things with a lot more care.

Notation

The grammar below uses a compact notation:

Notation Meaning
'if' the literal text if
a b a followed by b
`a b`
a? a, optionally
a* zero or more a
( … ) grouping

IDENT is a name, STRING a string literal, INT a whole number and FLOAT a fractional one.

Lexical structure

IDENT     = [a-zA-Z_] [a-zA-Z_0-9?]*
INT       = '0' | [1-9] [0-9]*
FLOAT     = INT '.' [0-9]+ EXPONENT? | INT EXPONENT
EXPONENT  = ('e' | 'E') ('+' | '-')? [0-9]+
STRING    = '"' (any character but '"' or '\', or an escape)* '"'
escape    = '\b' | '\t' | '\n' | '\f' | '\r' | '\"' | '\'' | '\\' | '\u' HEX HEX HEX HEX | '\' [0-7]{1,3}

A double quote inside a string is written \", as in "say \"hi\"".

  • Whitespace, including line breaks, only separates tokens. There are no semicolons; one statement simply follows another.
  • Comments are either // to the end of the line or /* anything */.

These words are reserved and cannot be used as names:

import  as      action  input   output  exec    group   let
if      else    repeat  while   break   continue
specify measure ask     true    false   nil
boolean string  number  range   fetch   from

Programs

program      = import* element*
import       = 'import' STRING ('as' IDENT)?

element      = action | input | output | group | exec
             | let | specify | measure | assign | metaAssign | statement

statement    = 'if' expression statement ('else' statement)?
             | 'repeat' (IDENT ':')? expression statement
             | 'while' expression statement
             | 'break'
             | 'continue'
             | expression
             | block

block        = '{' element* '}'

The body of an if, repeat or while is a single statement, usually a block. Conditions do not need parentheses, but most programs use them for readability: if (reps < 5) { … }.

Declarations

action       = 'action' IDENT ('<' qualifiedName)? keyValueList

input        = 'input' ':' typeHint IDENT keyValueList
             | 'input' IDENT '<' qualifiedName keyValueList
output       = 'output' ':' typeHint IDENT keyValueList
             | 'output' IDENT '<' qualifiedName keyValueList
typeHint     = 'boolean' | 'string' | 'number' | unit

group        = 'group' IDENT '(' params? ')' block
exec         = 'exec' IDENT '(' params? ')' block
params       = IDENT (',' IDENT)*

let          = 'let' IDENT ('=' expression)? (',' IDENT ('=' expression)?)*
assign       = IDENT ('.' position)* '=' expression
metaAssign   = '@' position ('.' position)* '=' expression

specify      = 'specify' (':' qualifiedName)? qualifiedName expression
               (',' qualifiedName expression)*
measure      = 'measure' qualifiedName (',' qualifiedName)*

Expressions

expression   = IDENT
             | literal
             | measurement
             | array
             | keyValueList
             | '(' expression ')'
             | expression '(' (expression (',' expression)*)? ')'
             | '@' position ('.' position)*
             | 'ask' '?'? qualifiedName
             | expression '..' '.'? expression
             | '..' '.'? expression
             | expression '..'
             | '&' expression
             | expression '.' position
             | expression '.' '(' expression ')'
             | ('-' | '!') expression
             | expression ('^' | '*' | '/' | '%' | '+' | '-') expression
             | expression ('<>' | '<' | '<=' | '>' | '>=' | '==' | '!=') expression
             | expression ('&&' | '||') expression
             | expression '?' expression ':' expression

literal      = INT | FLOAT | STRING | 'true' | 'false' | 'nil'
array        = '[' (expression (',' expression)*)? ']'
keyValueList = '[' ((IDENT | STRING) ':' expression
                    (',' (IDENT | STRING) ':' expression)*)? ']'

position     = IDENT | STRING | '-'? INT
qualifiedName = IDENT | IDENT '.' IDENT

Notice that [] is both an empty array and an empty map, and that the properties of an action, an input or an output are written as a map.

Operator precedence

When an expression mixes operators, they apply in the order below, from the one that binds tightest to the one that binds loosest. Operators on the same row apply from left to right, except ^ and ? :, which apply from right to left.

Operators Kind
f(…) call
.. ... range
& specified value
. access
- ! negation, not
^ power
* / % multiplication
+ - addition
<> containment
< <= > >= comparison
== != equality
&& and
`
? : conditional

Most of it works the way you would expect from school arithmetic, but a few consequences are worth knowing:

You write Coach reads Write instead
-2.5..8 -(2.5..8) (-2.5)..8
-2^2 (-2)^2, which is 4 -(2^2)
load.min..load.max (load.min..load).max (load.min)..(load.max)
&fdb.Squat.reps (&fdb).Squat.reps — this is what you want

Line breaks

Since line breaks do not end a statement, a line that starts with ( is read as a call of whatever ends the previous line:

let x = y
(a + b) * 2    // read as: let x = y(a + b) * 2

Start such a line with something else, for example by assigning the result to a name.

Measurements and units

measurement  = (INT | FLOAT) unit
unit         = '|' unitExpr '|'
unitExpr     = IDENT
             | unitExpr '^' exponent
             | unitExpr ('*' | '/') unitExpr
             | '(' unitExpr ')'
exponent     = '-'? INT ('/' INT)? | '(' exponent ')'

The names a unit is made of, and the prefixes they accept, are listed under measurements.