Syntax

Follows the entire Zink syntax and some examples.

Line continuation

Line continuations are used to tell the parser that the line where the line continuation character \ is and the one below it should be joined together.

Strings

Zink allows for 3 types of strings: the normal string "hello", byte strings b"hello" and regex strings r"myregex".

Raw strings

Zink also allows to include snippets of code that will be converted as-is: raw strings `print("Hello, World!")`. They can be either alone as statements or inside expressions.

Numbers

Numbers can be integers 10, decimals 0.10 (or abbreviated .10), hexadecimal 0x10 or binary 0b10. Any number of underscores are allowed anywhere in between, at the start and at the end (_1__0__10).

Line endings

Lines can be terminated with a newline character (pressing Enter on the keyboard to insert LF) or with a semicolon ; used to concatenate multiple lines on the same one. Only one is required at the end of a line, not both (putting a semicolon and pressing Enter should be avoided to prevent confusion).

Program

A Zink program is made of statements (expressions are also statements).

Naming convention

Variables must start with an uppercase or lowercase letter (A-Z) or with an underscore and can also contain numbers. Examples of this are myVar, myVar2, My_Var_2. This 2_myVar is not allowed.

Assignment

Variable assignment works exactly like in Python, where a variable a or a group of them a, b, c can be assigned to a value = 1 or multiple of them = 1, 2, 3. Variables can also be scoped with the dedicated keywords local, !local and global.

Compound assignment

In some cases, assignment can be abbreviated with a compound assignment operator. Here is a list of the normal assignment compared to the compound one:

Normal

Compound

a = a + b

a += b

a = a - b

a -= b

a = a * b

a *= b

a = a / b

a /= b

a = a . b

a .= b

a = a % b

a %= b

a = a ** b

a **= b

a = a // b

a //= b

a = a @ b

a @= b

a = a & b

a &= b

a = a | b

a |= b

a = a ^ b

a ^= b

a = ~ a

a ~

a = a << b

a <<= b

a = a >> b

a >>= b

self.a = a

@<-a

a = a .. b

a ..= b

a = b(a)

a to b

a = b.pop()

{b}->a

a = b.pop(c)

{b:c}->a

a.append(b)

{a}<-b

a.insert(b, c)

{a:b}<-c

General statements

These statements are made up of a keyword and can be followed by an expression. Here is a list of them:

Name

Description

assert a

Raises AssertionError if a is falsy.

raise a

Raises a.

use a

Imports the module named a.

use a as b

Imports the module named a as b.

use a from b

Imports a from the module named b.

use a as b from c

Imports a as c from the module named c.

pass

Doesn’t do anything.

next

Skips to the next iteration in a loop.

break

Exits out of the current loop.

global a

Specifies that the global a should be used.

global a = b

Assigns b to the global a.

!local a

Specifies that the nonlocal a should be used.

!local a = b

Assigns b to the nonlocal a.

local a

Specifies that the local a should be used.

local a = b

Assigns b to the local a.

<-

Returns None.

<-a

Returns a.

del a

Deletes a.

<|a

Prints a.

@a

Decorates a function with the decorator a.

Loops, conditional branches and error catching

While loop

while a
    ...
.

For loop

for a -> b
    ...
.

Enumerated for loop

for a at b -> c
    ...
.

Repetition

times x
    ...
.

Repetition with count

for i to a
    ...
.

Repetition with count and set start

for i from a to b
    ...
.

If branch

if a
    ...
.

If-else branch

if a
    ...
.else
    ...
.

If-elif branch

if a
    ...
.elif b
    ...
.

Elif branches can be chained one after the other.

If-elif-else branch

if a
    ...
.elif b
    ...
.else
    ...
.

Error catching

Try

When using try on its own, it will catch any exception.

try
    ...
.

Try-catch

try
    ...
.catch a
    ...
.

An exception object can be obtained like this:

try
    ...
.catch a = b
    ...
.

Catches can be chained one after the other.

Try-else

try
    ...
.else
    ...
.

Try-catch-else

try
    ...
.catch a
    ...
.else
    ...
.

Match and case

match a
    case b
        ...
    .
.

Cases can be chained one after the other.

For empty cases, use ignore instead:

match a
    ignore b
.

Functions

Functions are defined like this:

def a(b: type): type
    ...
.

Functions can have “attributes”:

=== asynchronous
def ?a(b)
    ...
.

=== method
def @a(b)
    ...
.

Functions can also be prefixed with one of the scoping keywords.

Dunder methods (__add__, __sub__ and so on) can also be declared with this other syntax (@ is implicit when declaring with /):

/add a, b, c
    ...
.

Except these, which instead have special names:

Symbol

Dunder method

*

__init__

+

__enter__

-

__exit__

!

__call__

To call a function:

a(b)

Anonymous functions

Some languages support defining functions without naming them. To do so in Zink, we use the anon keyword:

a = anon(b)
    ...
.

These functions can have attributes too:

=== asynchronous
a = anon?(b)
    ...
.

=== method
a = anon@(b)
    ...
.

Macros

Macros are functions that don’t need any argument. All function syntaxes are valid except the arguments, which are omitted:

def a: type
    ...
.

To call a macro:

a!

Lambdas

Another type of anonymous function is a lambda. Lambdas can be defined like this:

a = (x+y)<-(x, y)

Lambda macros can also be defined:

a = ("Hello, World!")<-!

Classes

class a
    ...
.

Classes can be prefixed with one of the scoping keywords.

Descendants

Descendants are classes that inherit code from other classes.

class a from b
    ...
.

With

with a = b
    ...
.

Lists, tuples and dictionaries

Lists

A list [a, b, c,] (note the comma) is defined as an expandable container for the same type of element. An example is a list of strings list(str) or a list of strings or numbers list(str | int). An empty list is defined as [,].

Tuples

A tuple [a, b, c] (note the missing comma) is defined as a set-size container for different types of elements. An example is a pair of coordinates tuple(10, 20). An empty tuple is defined as [].

Dictionaries

A dictionary [a=b, c=d] is defined as an expandable key-value container that associates a key to a value. An example is a dictionary of settings dict(str, bool). An empty dict is defined as [=].

Ranges

A range can be defined in 5 total ways:

  • Inclusive-Exclusive [0 <-> 4]0, 1, 2, 3, 4;

  • Inclusive-Exclusive [0 <-> 4)0, 1, 2, 3;

  • Exclusive-Inclusive (0 <-> 4]1, 2, 3, 4;

  • Exclusive-Exclusive (0 <-> 4)1, 2, 3;

  • Python default (Inclusive-Exclusive) {0 <-> 4}0, 1, 2, 3. This one should be used as much as possible since it also allows defining ranges that by default start from 0: {->4} → still 0, 1, 2, 3.

Incrementation and decrementation

Variables can be incremented and decremented (and also returned) before and after the operation is done:

  • Increment, then return: ++a

  • Decrement, then return: --a

  • Return, then increment: a++

  • Return, then decrement: a--

Contains operator

The contains operator a in b is used when it is needed to determine whether an element a is contained in a list, tuple or dictionary (as a key) b.

Abbreviations

Many parts of programming languages can seem very long and seem to serve no purpose. These abbreviations offer a solution to that problem:

Name

Description

↓a

Returns a converted to lowercase.

↑a

Returns a converted to uppercase.

#a

Returns the length of a.

a?

Returns the type of a.

a as b

Returns a converted to b (b(a)).

a like b

Returns a converted to the type of b (type(b)(a)).

@

Returns self.

^

Returns super().

@^

Returns super().__init__.

a between b, c

Returns b <= a <= c.

Dollar

To eliminate unnecessary repetition of the same variable, the dollar $ is used. The dollar defaults to empty and can be any expression. Here are some examples:

Dollar

Equivalent

a += $

a += a

a = a[b]

a = $[b]

a.b(a, c)

a.b$(c)

Comments

Finally, comments need to be at the start of a line and are defined like this:

=== this is a line comment!

Multiline comments are still defined like line comments:

=== This is a multiline comment.
=== It spans multiple lines.