Whitespace
Unlike most languages, whitespace matters in Z#. Similar to Python and F#.
Spaces and Tabs
Spaces are used to separate code elements in order to make them distinct.
iftrue
does not mean anything, while if true
represents an if
statement with a condition (true
). The space separates the parts and makes them meaningful.
Indentation
Z# uses whitespace -indents- to signal scope. Where a lot of languages use {}
to declare a scope, Z# attempts to minimize noise and do away with those characters.
An indent
is represented by a sequence of spaces.
Typically 4 spaces are used. The number of spaces must be consistent throughout a single file but can differ between files. The first indent that is encountered is taken as the template for all others to follow.
The start of the line represents the root scope. An indent
is always placed below a statement or instruction that belongs to a higher scope (one up).
A simple example of a function and its implementation:
MyFuncion: ()
implementation_here
more_code_here
The function is at root scope and all of its implementation is one indent removed from that origin.
These scopes can of course be nested.
Here is an example of a function with nested scopes.
MyFuncion: ()
if true
conditional_code_here
implementation_here
The ‘conditional code’ is only executed if the if
branch is taken. The ‘implementation’ is always executed.
Scope Names
Names for the indentation levels that create scopes:
global-scope // exports
<File>
top-level (file-scope)
<Function>
function-scope
<Type>
type-scope
Breaking up lines
As a general rule: broken up lines continue on the next line one indent further than a new (child) scope would be: a double indent.
Literal Strings
No extra quotes are required. The new-line (EOL) character(s) and the indent white-space(s) will NOT become part of the string.
s := "this is a very long string to demonstrate
how to break up long literal strings"
So in this example there is a trailing space after ‘demonstrate’ in order to separate that word from the next word ‘how’.
Functions
The function name cannot be split. Parameters can be spread out over multiple lines. The opening parenthesis is always next to the function name. The closing parenthesis can either be directly after the last parameter or at the beginning (same indent/scope as the function name) of a new line. Commas that separate the parameters go directly after its previous parameter and cannot be on the start of a new line (after a double indent).
my_very_long_function_name_with_lots_of_parameters: (
p1: U8, p2: U16, p3: U32, p4: U64,
p5: Str
)
...
Use new-lines instead of commas.
function_with_params_but_no_commas: (
p1: U8
p2: U16
p3: U32
p4: U64
p5: Str
)
...
The
()
are becoming the list delimiters. Does not using commas also apply to general lists?
lst := (
1
2
3
4
5
6
)
// would be the same as
lst := (1, 2, 3, 4, 5, 6)
Merging lines
Use the ->
operator to continue a line of code.
fn: ()
// you cannot write this
if true doStuff() -> else doNothing()
// use this instead
true ? doStuff() : doNothing()
a := 42
// useful for match case continuation
x := match a
Error -> "This is an error"
U8 -> "This is an unsigned 8-bit.
_ -> "I don't know what this is"
// also used for lambda's
spaces := x.Where((c) -> c = ' ')
Comments
Comments can really throw of the flow of a block. Therefor comments must start at the same indentation as the block.
Example of hard to read code:
// indentation block
fn: ()
a := 42
x := 101
// comment
y := 2112 // unclear if this belongs to the block above the comment
Corrected example:
// indentation block
fn: ()
a := 42
x := 101
// comment
y := 2112
TBD: Ignore comments in determining indentation.
TBD
Allow {}
as an escape of indent based parsing.
When after certain keywords an opening {
is detected, the indentation rules are dropped and the closing }
ends the code block.
This could be a nice balance between both types of block detection mechanisms.
{}
could also be used as an inline block.