The complete grammar: how Pliro fits together

Syntax and values

Grammar describes which combinations of words and symbols form valid code. This page uses a compact technical notation called EBNF. You do not need to memorize it to start programming; use the linked beginner articles first.

How to read the notation

EBNF means Extended Backus–Naur Form. It is a way to write rules about a language. Think of it as a recipe that says which ingredients may go where. It is not Pliro code to run.

Notation How to read it
name = ... ; The rule called name consists of the parts on the right. The semicolon ends this rule.
"let" The quoted word or symbol is written literally in the program.
a, b First a, then b.
a | b Choose a or b.
[ a ] a is optional: it may be left out.
{ a } Repeat a zero or more times.
( a | b ) Keep these alternatives together as a group.
(* ... *) An explanation for the reader of the grammar.

Unquoted names refer to other rules. For example, identifier means a name, expression means code that produces a value, and statement means an instruction. newline ends a line and EOF (end of file) means there is no source text left. INDENT and DEDENT mark entering and leaving an indented block. Comments are ignored for these grammar rules; the technical word trivia here means text that does not change the program’s instructions.

A token is one recognized piece of code, such as a name, a number, or +. The parser puts these pieces together using the grammar. &TOKEN means “check that this piece comes next, but leave it for the surrounding rule to handle.” The punctuation in this table describes the grammar; quoted punctuation inside a rule describes what to type in Pliro.

This EBNF describes the implemented canonical grammar using English spellings.
INDENT and DEDENT are produced from leading spaces. Comments are omitted as
trivia.

program          = { newline | statement }, EOF ;

statement        = useStatement
                 | letStatement
                 | setStatement
                 | ifStatement
                 | whileStatement
                 | forStatement
                 | functionDeclaration
                 | eventHandler
                 | returnStatement
                 | commandCallStatement
                 | expressionStatement ;

useStatement     = "use", moduleName, lineEnd ;
moduleName       = identifier, { ".", identifier } ;

letStatement     = "let", identifier, "=", expression, lineEnd ;

setStatement     = "set", assignmentTarget, "=", expression, lineEnd ;
assignmentTarget = identifier,
                   { "[", expression, "]" | ".", identifier } ;

ifStatement      = "if", expression, block,
                   [ "else", block ] ;

whileStatement   = "while", expression, block ;

forStatement     = "for", identifier, "in", expression, block ;

functionDeclaration
                 = "function", identifier,
                   "(", [ parameters ], ")", block ;
parameters       = identifier, { ",", identifier }, [ "," ] ;

eventHandler     = "when", expression, block ;

returnStatement  = "return", [ expression ], lineEnd ;

commandCallStatement
                 = commandTarget, commandFirstArgument,
                   { ",", expression }, [ "," ], lineEnd ;
commandTarget    = identifier, { ".", identifier } ;
commandFirstArgument
                 = expression
                   (* first token: identifier, number, string, true, false,
                      none, not, or "{" *) ;

expressionStatement
                 = expression, lineEnd ;

block            = ":", newline, { newline }, INDENT,
                   statement, { newline | statement }, DEDENT ;

lineEnd          = newline | &DEDENT | &EOF ;

expression       = logicalOr ;
logicalOr        = logicalAnd, { "or", logicalAnd } ;
logicalAnd       = equality, { "and", equality } ;
equality         = comparison, { ( "==" | "!=" ), comparison } ;
comparison       = additive, { ( "<" | "<=" | ">" | ">=" ), additive } ;
additive         = multiplicative, { ( "+" | "-" ), multiplicative } ;
multiplicative   = unary, { ( "*" | "/" | "%" ), unary } ;
unary            = ( "not" | "+" | "-" ), unary | postfix ;
postfix          = primary, { callSuffix | indexSuffix | memberSuffix } ;
callSuffix       = "(", [ arguments ], ")" ;
arguments        = expression, { ",", expression }, [ "," ] ;
indexSuffix      = "[", expression, "]" ;
memberSuffix     = ".", identifier ;

primary          = number
                 | string
                 | "true"
                 | "false"
                 | "none"
                 | identifier
                 | "(", expression, ")"
                 | listLiteral
                 | mapLiteral ;

listLiteral      = "[", [ arguments ], "]" ;
mapLiteral       = "{", [ mapEntry,
                   { ",", mapEntry }, [ "," ] ], "}" ;
mapEntry         = expression, ":", expression ;

&TOKEN means that the parser requires that token next without consuming it;
the enclosing block or program consumes the eventual DEDENT or EOF.

For the command form, the first argument may begin with an identifier, number,
text literal, true, false, none, not, or a map literal. Use a
parenthesized call when the first argument begins with unary + or -, a
grouping (, or a list literal [. See Section 5.4.

Syntax and values · Built-ins · Programming guides