Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time
172 lines (125 sloc) 7.47 KB

Checktestdata language specification

This specification is dedicated to the public domain. Its authors waive all rights to the work worldwide under copyright law, including all related and neighboring rights, as specified in the Creative Commons Public Domain Dedication (CC0 1.0).

Grammar and command syntax below. A valid checktestdata program consists of a list of commands. All commands are uppercase, while variables are lowercase with non-leading digits. Comments start with # (not necessarily at the beginning of a line) and run until end of line.

The following grammar sub-elements are defined:

integer  := 0|-?[1-9][0-9]*
float    := -?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?
string   := ".*"
varname  := [a-z][a-z0-9]*
variable := <varname> | <varname> '[' <expr> [',' <expr> ...] ']'
value    := <integer> | <float> | <string> | <variable> | <function>
compare  := '<' | '>' | '<=' | '>=' | '==' | '!='
logical  := '&&' | '||'
expr     := <term> | <expr> [+-] <term>
term     := <factor> | <term> [*%/] <factor>
factor   := <value> | '-' <factor> | '(' <expr> ')' | <factor> '^' <factor>
test     := '!' <test> | <test> <logical> <test> | '(' <test> ')' |
             <expr> <compare> <expr> | <testcommand>

That is, variables can take integer, floating point as well as string values. No dynamic casting is performed, except that integers can be cast into floats. Integers and floats of arbitrary size and precision are supported, as well as the arithmetic operators +-*%/^ with the usual rules of precedence. An expression is integer if all its sub-expressions are integer. Integer division is used on integers. The exponentiation operator ^ only allows non-negative integer exponents that fit in an unsigned long. String-valued variables can only be compared (lexicographically), no operators are supported.

Within a string, the backslash acts as escape character for the following expressions:

  • \[0-7]{1,3} denotes an octal escape for a character
  • \n, \t, \r, \b denote linefeed, tab, carriage return and backspace
  • \" and \\ denote " and \
  • an escaped newline is ignored (line continuation)

A backslash preceding any other character is treated as a literal backslash.

Tests can be built from comparison operators, the usual logical operators ! && || (not, and, or) and a number of test commands that return a boolean value. These are:

MATCH(<value> str)
Returns whether the next character matches any of the characters in 'str', which must have string type.
Returns whether end-of-file has been reached.
UNIQUE(<varname> a [,<varname> b ...])
Checks for uniqueness of tuples of values in the combined (array) variables a, b, ... That is, it is checked that firstly all arguments have precisely the same set of indices defined, and secondly that the tuples formed by evaluating (a,b,...) at these indices are unique. For example, if x,y are 1D arrays containing coordinates, then UNIQUE(x,y) checks that the points (x[i],y[i]) in the plane are unique.
INARRAY(<value> val, <varname> var)
Checks if val occurs in the array variable var.

The following functions are available:

STRLEN(<value> str)
Returns the character length of 'str', which must have string type.

The following commands are available:

No-argument commands matching a single space (0x20) or newline respectively.
Matches end-of-file. This is implicitly added at the end of each program and must match exactly: no extra data may be present.
INT(<expr> min, <expr> max [, <variable> name])
Match an arbitrary sized integer value in the interval [min,max] and optionally assign the value read to variable 'name'.
FLOAT(<expr> min, <expr> max [, <variable> name [, option]])
Match a floating point number in the range [min,max] and optionally assign the value read to the variable 'name'. When the option 'FIXED' or 'SCIENTIFIC' is set, only accept floating point numbers in fixed point or scientific notation, respectively.
FLOATP(<expr> min, <expr> max, <value> mindecimals, <value> maxdecimals, [, <variable> name [, option]])
Match a floating point number as above, but with number of decimals in the range [mindecimals,maxdecimals]. Both must be integer valued and non-negative. In this case a floating point number in scientific notation must have exactly one nonzero digit before the decimal point.
STRING(<value> str)
Match the string (variable) 'str'.
REGEX(<value> str [, <variable> name])
Match the extended regular expression 'str', which must be of string type. Matching is performed greedily and '.' (a dot) matches a newline, use [^\n] to work around this. Optionally assign the matched string to variable 'name'. Note that since some string characters have to be escaped already, you might need to double escape them in a regex string. For example, to match a literal backslash, enter \\\\.
ASSERT(<test> condition)
Assert that 'condition' is true, fail otherwise.
SET(<variable> name '=' <expr> value[, ...])
Assign 'value' to variable 'name', etc...
UNSET(<varname> a [,<varname> b ...])
Unset all values for variables a, b, ... This includes all values for array indexed variables with these names. This command should typically be inserted at the end of a loop after using UNIQUE or INARRAY, to make sure that no old variables are present anymore during the next iteration.
REP(<expr> count [,<command> separator]) [<command>...] END
REPI(<variable> i, <expr> count [,<command> separator]) [<command>...] END
Repeat the commands between the 'REP() ... END' statements count times and optionally match 'separator' command (count-1) times in between. The value of count must fit in an unsigned 32 bit int. The second command 'REPI' does the same, but also stores the current iteration (counting from zero) in the variable 'i'. At the end of the loop, 'i' contains the number of iterations.
WHILE(<test> condition [,<command> separator]) [<command>...] END
WHILEI(<variable> i, <test> condition [,<command> separator]) [<command>...] END
Repeat the commands as long as 'condition' is true. Optionally match 'separator' command between two consecutive iterations. The second command 'WHILEI' does the same, but also stores the current iteration (counting from zero) in the variable 'i'. At the end of the loop, 'i' contains the number of iterations.
IF(<test> cond) [<command> cmds1...] [ELSE [<command> cmds2...]] END
Executes cmds1 if cond is true. Otherwise, executes cmds2 if the else statement is available.