The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
%grammar testml
%version 0.0.1
%include atom

testml-document:
  code-section
  data-section?

# General Tokens
escape: / [0nt] /
line: / ANY* EOL /
blanks: / BLANK+ /
blank-line: / BLANK* EOL /
comment: / '#' line /
ws: /(: BLANK | EOL | comment )/

# Strings
quoted-string:
  | single-quoted-string
  | double-quoted-string

single-quoted-string:
  /(:
    SINGLE
    ((:
      [^ BREAK BACK SINGLE ] |
      BACK SINGLE |
      BACK BACK
    )*?)
    SINGLE
  )/

double-quoted-string:
  /(:
    DOUBLE
    ((:
      [^ BREAK BACK DOUBLE] |
      BACK DOUBLE |
      BACK BACK |
      BACK escape
    )*?)
    DOUBLE
  )/

unquoted-string:
  /(
    [^ BLANKS BREAK HASH]
    (:
      [^ BREAK HASH]*
      [^ BLANKS BREAK HASH]
    )?
  )/

number: / ( DIGIT+ ) /


# TestML Code Section
code-section: (
  | +
  | assignment-statement
  | code-statement
)*

assignment-statement:
  variable-name
  / WS+ '=' WS+ /
  code-expression
  ending

variable-name: /( ALPHA WORD* )/

code-statement:
  code-expression
  assertion-call?
  ending

ending: /(: ';' | EOL )/ | =ending2

ending2: /- '}'/

code-expression:
  code-object
  call-call*

call-call:
  !assertion-call-test
  call-indicator
  code-object

code-object:
  | function-object
  | point-object
  | string-object
  | number-object
  | call-object

function-object:
  function-signature?
  function-start
  ( + | assignment-statement | code-statement )*
  /- '}'/

function-start: /- ( '{' ) -/

function-signature:
  /'(' -/
  function-variables?
  /- ')'/

function-variables:
  function-variable+ % /- ',' -/

function-variable: /( ALPHA WORD* )/

point-object: /( '*' LOWER WORD* )/

string-object: quoted-string

number-object: number

call-object:
  call-name
  call-argument-list?

call-name: user-call | core-call

user-call: /( LOWER WORD* )/

core-call: /( UPPER WORD* )/

call-indicator: /(: '.' - | - '.' )/

call-argument-list:
  /'(' -/
  call-argument* % /- ',' -/
  /- ')'/

call-argument: code-expression

assertion-call-test: / call-indicator (:EQ|OK|HAS) /

assertion-call:
  | +assertion-eq
  | +assertion-ok
  | +assertion-has

assertion-eq:
  | +assertion-operator-eq
  | +assertion-function-eq

assertion-operator-eq:
  /+ '==' +/
  code-expression

assertion-function-eq:
  / call-indicator 'EQ(' /
  code-expression
  / ')' /

assertion-ok: assertion-function-ok

assertion-function-ok: / call-indicator ('OK') empty-parens? /

assertion-has: +assertion-operator-has | +assertion-function-has

assertion-operator-has:
  /+ '~~' +/
  code-expression

assertion-function-has:
  / call-indicator 'HAS(' /
  code-expression
  / ')' /

empty-parens: /(: '(' - ')' )/

# TestML Data Section
block-marker: '==='
point-marker: '---'

data-section: data-block*

data-block:
  block-header
  .( blank-line | comment )*
  block-point*

block-header:
  block-marker
  ( blanks block-label )?
  blank-line

block-label: unquoted-string

block-point: lines-point | phrase-point

lines-point:
  point-marker
  blanks
  point-name
  blank-line
  point-lines

point-lines: /(
  (:
    (!
      (: block-marker | point-marker )
      SPACE WORD
    )
    line
  )*
)/

phrase-point:
  point-marker
  blanks
  point-name
  / COLON BLANK /
  point-phrase
  / EOL /
  /(:
    comment |
    blank-line
  )*/

point-name:
  /(
    user-point-name |
    core-point-name
  )/

user-point-name:  / LOWER WORD* /

core-point-name:  / UPPER WORD* /

point-phrase: unquoted-string

# vim: sw=2 lisp: