This document describes the syntax of Snekky source code in EBNF as proposed in ISO/IEC 14977.
Table of Contents
In computer science, extended Backus–Naur form (EBNF) is a family of metasyntax notations, any of which can be used to express a context-free grammar. EBNF is used to make a formal description of a formal language such as a computer programming language. They are extensions of the basic Backus–Naur form (BNF) metasyntax notation.
– Wikipedia
Usage | Notation |
---|---|
definition | = |
concatenation | , |
termination | ; |
alternation | | |
optional | [ ... ] |
repetition | { ... } |
grouping | ( ... ) |
terminal string | " ... " |
terminal string | ' ... ' |
comment | (* ... *) |
Letter = "a" | ... | "z" | "A" | ... | "Z";
Number = "0" | "1" | "2" | "4" | "5" | "6" | "7" | "8" | "9";
Hex = "a" | ... | "f" | "A" | ... | "F" | Number;
UnicodeChar = (* any Unicode character *)
An identifier is a name assigned to an element in a program. It has to start with either a letter or an underscore followed by an arbitrary amount of alphanumeric characrers.
Identifier = ( Letter | "_" ) { ( Letter | Number | "_" ) };
Examples of valid identifiers are myVariable
, TeSt123
, _2ident
, and x
. An example of an invalid identifier is 0test
.
A literal produces a value which is represented as an object.
Literal = NumberLiteral | StringLiteral | BooleanLiteral | NullLiteral | ArrayLiteral | HashLiteral | FunctionLiteral;
A number literal may represent any unsigned floating point number expressed either in decimal or hexadecimal notation.
NumberLiteral = DecNumber | HexNumber;
DecNumber = Number, { Number };
HexNumber = "0x", Hex, { Hex };
Examples:
42
3.452
0xA6C8DD
A string literal represents any UTF-8-encoded string enclosed by quotation marks.
StringLiteral = '"', { UnicodeChar }, '"';
Examples:
"This is a string!"
A boolean literal represents a truth value and may either be true
or false
.
BooleanLiteral = "true" | "false";
true
false
Null represents the absence of a value.
NullLiteral = "null";
An array literal represents a collection of indexed literals.
ArrayLiteral = "[", ExpressionList, "]";
Examples:
[3, "my string", true]
A hash literal represents a key-value-collection of literals.
HashLiteral = "{", [ HashKeyValue, { ",", HashKeyValue } ], "}";
HashKeyValue = ( StringLiteral | Identifier ) , ":", Expression;
Examples:
{
"string": true,
ident: 2.3
}
{}
A function literal represents a higher-order function. It may take a list of arguments and may return a value.
FunctionLiteral = "func", "(", FunctionParameterList, ")", Block;
FunctionParameterList = [ FunctionParameter, { ",", FunctionParameter } ];
FunctionParameter = [ "mut" ], Identifier;
Examples:
func(x, mut y) {
y += 2;
return x + y;
}
An expression generates an object at runtime.
Expression = BinaryExpression | UnaryExpression | BracketedExpression | Literal | RegexExpression | ExpressionStatement | AccessExpression;
BracketedExpression = "(", Expression, ")";
BinaryExpression = Expression, BinaryOperator, Expression;
UnaryExpression = UnaryOperator, Expression;
BinaryOperator = "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", "&&", "||", "&", "|", "<<", ">>", "^", "><";
UnaryOperator = "-", "~";
ExpressionList = [ ( Expression, { ",", Expression } ) ];
Examples:
2 * (3 + 4)
4
true
-3.324345
A call expression calls a function which may return a value.
CallExpression = Expression, "(", ExpressionList, ")";
Examples:
add(1, 2)
println("Hello, World!")
A regex expression generates a regex object. It only serves as syntactical sugar for Regex.compile
.
RegexExpression = "~/", { UnicodeChar }, "/";
Examples:
~/a[bc]*d/
An access expression evaluates to the value stored at the given index in an array or hash.
AccessExpression = ArrayAccess | DotAccess;
ArrayAccess = Expression, "[", Expression, "]";
DotAccess = Expression, ".", Identifier;
Examples:
myArray[3]
Sys.println
A statement is a direct instruction telling the computer to do a certain thing.
Statement = VariableDeclarationStatement | VariableAssignStatement | WhileStatement | ForStatement | ReturnStatement | ImportStatement | BreakStatement | ContinueStatement | ExpressionStatement | ExpressionableStatement;
Some statements may itself be used as an expression when their last statement is an expression and its semicolon is omitted.
ExpressionableStatement = BlockStatement | IfStatement | WhenStatement;
Examples:
let x = if y > 5 {
5
} else {
y
};
A block statement (or compound statement) combines multiple statements into one creating a new lexical scope.
BlockStatement = "{", Statement, { Statement }, [ Expression ] "}";
Examples:
let x = 2;
{
let x = 2;
}
A variable is an abstract storage location paired with an associated symbolic name. That symbolic name may either be an ident or a destructuring statement.
VariableDeclarationStatement = VariableDeclaration, "=", Expression ";";
VariableDeclaration = VariableMutability, VariableName;
VariableMutability = "let" | "mut";
VariableName = Identifier | ( "[", Identifier, { ",", Identifier } "]" );
Examples:
let myVariable = 1 * (2 + 3);
let [x, y , z] = [1, 2, 3];
mut {statusCode} = {statusCode: 200, text: "Request succeeded!"};
VariableAssignStatement = Identifier, AssignOperator, Expression;
AssignOperator = "=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "<<=", ">>=", "^=";
Examples:
myVariable = 42;
i += 1;
An if statement executes a corresponding piece of code depending on whether the given condition is met.
IfStatement = "if", Expression, BlockStatement, [ "else", BlockStatement ];
Examples:
if x == 2 {
Sys.println("x is 2");
}
if arr.length() == 0 {
return false;
} else {
return true;
}
A while statement executes a piece of code as long as the given condition is met.
WhileStatement = "while", Expression, BlockStatement;
Examples:
mut x = 0;
while x < 5 {
Sys.println(x);
x += 1;
}
A for statement executes a piece of code until is underlying iterator has reached its end.
ForStatement = "for", [ VariableDeclaration, "in" ], Expression, BlockStatement;
Examples:
for let [v, i] in [5, 2, 6, 4] {
Sys.println(i);
Sys.println(v);
}
for 0...5 {
Sys.println("executed");
}
A when statement is syntactical sugar for a chain of else-if-statements.
WhenStatement = "when", [ Expression ], "{", { Expression, "=>", Expression }, [ "else", "=>", Expression ] "}";
Examples:
let x = 2;
when x {
1 => Sys.println("x == 1");
2 => Sys.println("x == 2");
else => {
Sys.println("x is neither 1 nor 2");
}
}
when {
x >= 2 => Sys.println("x >= 2");
else => Sys.println("x < 2");
}
A return statement immediately returns from the current function call. It may return a value.
ReturnStatement = "return", [ Expression ], ";";
Examples:
return 2 * 3;
return;
A continue statement aborts the current iteration of a loop and jumps back to the beginning of said loop's block.
ContinueStatement = "continue", ";";
Exmaples:
mut i = 0;
while true {
i += 1;
if i < 5 {
continue;
}
Sys.println("i is greater than 5: " >< i);
}
A break statement immediately aborts the execution of loop ignoring its condition.
BreakStatement = "break", ";";
Examples:
mut i = 0;
while true {
i += 1;
if i > 5 {
break;
}
}
Sys.println("loop aborted because i was greater than 5: " >< i);
An import statement pastes the content of the given file into this file at the current position. The .snek
extension has to be omitted.
ImportStatement = "import", StringLiteral, ";";
Examples:
import "my_file";
Any expression may also be a statement when followed by a semicolon.
ExpressionStatement = Expression, ";";
2 * (3 - 4);
test();