mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-05 04:27:44 +00:00
690 lines
20 KiB
Plaintext
690 lines
20 KiB
Plaintext
Statements and expressions
|
|
==========================
|
|
|
|
Nim uses the common statement/expression paradigm: Statements do not
|
|
produce a value in contrast to expressions. However, some expressions are
|
|
statements.
|
|
|
|
Statements are separated into `simple statements`:idx: and
|
|
`complex statements`:idx:.
|
|
Simple statements are statements that cannot contain other statements like
|
|
assignments, calls or the ``return`` statement; complex statements can
|
|
contain other statements. To avoid the `dangling else problem`:idx:, complex
|
|
statements always have to be indented. The details can be found in the grammar.
|
|
|
|
|
|
Statement list expression
|
|
-------------------------
|
|
|
|
Statements can also occur in an expression context that looks
|
|
like ``(stmt1; stmt2; ...; ex)``. This is called
|
|
an statement list expression or ``(;)``. The type
|
|
of ``(stmt1; stmt2; ...; ex)`` is the type of ``ex``. All the other statements
|
|
must be of type ``void``. (One can use ``discard`` to produce a ``void`` type.)
|
|
``(;)`` does not introduce a new scope.
|
|
|
|
|
|
Discard statement
|
|
-----------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
proc p(x, y: int): int =
|
|
result = x + y
|
|
|
|
discard p(3, 4) # discard the return value of `p`
|
|
|
|
The ``discard`` statement evaluates its expression for side-effects and
|
|
throws the expression's resulting value away.
|
|
|
|
Ignoring the return value of a procedure without using a discard statement is
|
|
a static error.
|
|
|
|
The return value can be ignored implicitly if the called proc/iterator has
|
|
been declared with the `discardable`:idx: pragma:
|
|
|
|
.. code-block:: nim
|
|
proc p(x, y: int): int {.discardable.} =
|
|
result = x + y
|
|
|
|
p(3, 4) # now valid
|
|
|
|
An empty ``discard`` statement is often used as a null statement:
|
|
|
|
.. code-block:: nim
|
|
proc classify(s: string) =
|
|
case s[0]
|
|
of SymChars, '_': echo "an identifier"
|
|
of '0'..'9': echo "a number"
|
|
else: discard
|
|
|
|
|
|
Void context
|
|
------------
|
|
|
|
In a list of statements every expression except the last one needs to have the
|
|
type ``void``. In addition to this rule an assignment to the builtin ``result``
|
|
symbol also triggers a mandatory ``void`` context for the subsequent expressions:
|
|
|
|
.. code-block:: nim
|
|
proc invalid*(): string =
|
|
result = "foo"
|
|
"invalid" # Error: value of type 'string' has to be discarded
|
|
|
|
.. code-block:: nim
|
|
proc valid*(): string =
|
|
let x = 317
|
|
"valid"
|
|
|
|
|
|
Var statement
|
|
-------------
|
|
|
|
Var statements declare new local and global variables and
|
|
initialize them. A comma separated list of variables can be used to specify
|
|
variables of the same type:
|
|
|
|
.. code-block:: nim
|
|
|
|
var
|
|
a: int = 0
|
|
x, y, z: int
|
|
|
|
If an initializer is given the type can be omitted: the variable is then of the
|
|
same type as the initializing expression. Variables are always initialized
|
|
with a default value if there is no initializing expression. The default
|
|
value depends on the type and is always a zero in binary.
|
|
|
|
============================ ==============================================
|
|
Type default value
|
|
============================ ==============================================
|
|
any integer type 0
|
|
any float 0.0
|
|
char '\\0'
|
|
bool false
|
|
ref or pointer type nil
|
|
procedural type nil
|
|
sequence nil (*not* ``@[]``)
|
|
string nil (*not* "")
|
|
tuple[x: A, y: B, ...] (default(A), default(B), ...)
|
|
(analogous for objects)
|
|
array[0..., T] [default(T), ...]
|
|
range[T] default(T); this may be out of the valid range
|
|
T = enum cast[T](0); this may be an invalid value
|
|
============================ ==============================================
|
|
|
|
|
|
The implicit initialization can be avoided for optimization reasons with the
|
|
`noinit`:idx: pragma:
|
|
|
|
.. code-block:: nim
|
|
var
|
|
a {.noInit.}: array [0..1023, char]
|
|
|
|
If a proc is annotated with the ``noinit`` pragma this refers to its implicit
|
|
``result`` variable:
|
|
|
|
.. code-block:: nim
|
|
proc returnUndefinedValue: int {.noinit.} = discard
|
|
|
|
|
|
The implicit initialization can be also prevented by the `requiresInit`:idx:
|
|
type pragma. The compiler requires an explicit initialization for the object
|
|
and all of its fields. However it does a `control flow analysis`:idx: to prove
|
|
the variable has been initialized and does not rely on syntactic properties:
|
|
|
|
.. code-block:: nim
|
|
type
|
|
MyObject = object {.requiresInit.}
|
|
|
|
proc p() =
|
|
# the following is valid:
|
|
var x: MyObject
|
|
if someCondition():
|
|
x = a()
|
|
else:
|
|
x = a()
|
|
use x
|
|
|
|
|
|
let statement
|
|
-------------
|
|
|
|
A ``let`` statement declares new local and global `single assignment`:idx:
|
|
variables and binds a value to them. The syntax is the same as that of the ``var``
|
|
statement, except that the keyword ``var`` is replaced by the keyword ``let``.
|
|
Let variables are not l-values and can thus not be passed to ``var`` parameters
|
|
nor can their address be taken. They cannot be assigned new values.
|
|
|
|
For let variables the same pragmas are available as for ordinary variables.
|
|
|
|
|
|
Tuple unpacking
|
|
---------------
|
|
|
|
In a ``var`` or ``let`` statement tuple unpacking can be performed. The special
|
|
identifier ``_`` can be used to ignore some parts of the tuple:
|
|
|
|
.. code-block:: nim
|
|
proc returnsTuple(): (int, int, int) = (4, 2, 3)
|
|
|
|
let (x, _, z) = returnsTuple()
|
|
|
|
|
|
|
|
Const section
|
|
-------------
|
|
|
|
`Constants`:idx: are symbols which are bound to a value. The constant's value
|
|
cannot change. The compiler must be able to evaluate the expression in a
|
|
constant declaration at compile time.
|
|
|
|
Nim contains a sophisticated compile-time evaluator, so procedures which
|
|
have no side-effect can be used in constant expressions too:
|
|
|
|
.. code-block:: nim
|
|
import strutils
|
|
const
|
|
constEval = contains("abc", 'b') # computed at compile time!
|
|
|
|
|
|
The rules for compile-time computability are:
|
|
|
|
1. Literals are compile-time computable.
|
|
2. Type conversions are compile-time computable.
|
|
3. Procedure calls of the form ``p(X)`` are compile-time computable if
|
|
``p`` is a proc without side-effects (see the `noSideEffect pragma
|
|
<#pragmas-nosideeffect-pragma>`_ for details) and if ``X`` is a
|
|
(possibly empty) list of compile-time computable arguments.
|
|
|
|
|
|
Constants cannot be of type ``ptr``, ``ref``, ``var`` or ``object``, nor can
|
|
they contain such a type.
|
|
|
|
|
|
Static statement/expression
|
|
---------------------------
|
|
|
|
A static statement/expression can be used to enforce compile
|
|
time evaluation explicitly. Enforced compile time evaluation can even evaluate
|
|
code that has side effects:
|
|
|
|
.. code-block::
|
|
|
|
static:
|
|
echo "echo at compile time"
|
|
|
|
It's a static error if the compiler cannot perform the evaluation at compile
|
|
time.
|
|
|
|
The current implementation poses some restrictions for compile time
|
|
evaluation: Code which contains ``cast`` or makes use of the foreign function
|
|
interface cannot be evaluated at compile time. Later versions of Nim will
|
|
support the FFI at compile time.
|
|
|
|
|
|
If statement
|
|
------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
|
|
var name = readLine(stdin)
|
|
|
|
if name == "Andreas":
|
|
echo "What a nice name!"
|
|
elif name == "":
|
|
echo "Don't you have a name?"
|
|
else:
|
|
echo "Boring name..."
|
|
|
|
The ``if`` statement is a simple way to make a branch in the control flow:
|
|
The expression after the keyword ``if`` is evaluated, if it is true
|
|
the corresponding statements after the ``:`` are executed. Otherwise
|
|
the expression after the ``elif`` is evaluated (if there is an
|
|
``elif`` branch), if it is true the corresponding statements after
|
|
the ``:`` are executed. This goes on until the last ``elif``. If all
|
|
conditions fail, the ``else`` part is executed. If there is no ``else``
|
|
part, execution continues with the next statement.
|
|
|
|
In ``if`` statements new scopes begin immediately after the ``if``/``elif``/``else`` keywords and ends after the corresponding *then* block.
|
|
For visualization purposes the scopes have been enclosed in ``{| |}`` in the following example:
|
|
|
|
.. code-block:: nim
|
|
if {| (let m = input =~ re"(\w+)=\w+"; m.isMatch):
|
|
echo "key ", m[0], " value ", m[1] |}
|
|
elif {| (let m = input =~ re""; m.isMatch):
|
|
echo "new m in this scope" |}
|
|
else: {|
|
|
echo "m not declared here" |}
|
|
|
|
Case statement
|
|
--------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
|
|
case readline(stdin)
|
|
of "delete-everything", "restart-computer":
|
|
echo "permission denied"
|
|
of "go-for-a-walk": echo "please yourself"
|
|
else: echo "unknown command"
|
|
|
|
# indentation of the branches is also allowed; and so is an optional colon
|
|
# after the selecting expression:
|
|
case readline(stdin):
|
|
of "delete-everything", "restart-computer":
|
|
echo "permission denied"
|
|
of "go-for-a-walk": echo "please yourself"
|
|
else: echo "unknown command"
|
|
|
|
|
|
The ``case`` statement is similar to the if statement, but it represents
|
|
a multi-branch selection. The expression after the keyword ``case`` is
|
|
evaluated and if its value is in a *slicelist* the corresponding statements
|
|
(after the ``of`` keyword) are executed. If the value is not in any
|
|
given *slicelist* the ``else`` part is executed. If there is no ``else``
|
|
part and not all possible values that ``expr`` can hold occur in a
|
|
``slicelist``, a static error occurs. This holds only for expressions of
|
|
ordinal types. "All possible values" of ``expr`` are determined by ``expr``'s
|
|
type. To suppress the static error an ``else`` part with an
|
|
empty ``discard`` statement should be used.
|
|
|
|
For non ordinal types it is not possible to list every possible value and so
|
|
these always require an ``else`` part.
|
|
|
|
As a special semantic extension, an expression in an ``of`` branch of a case
|
|
statement may evaluate to a set or array constructor; the set or array is then
|
|
expanded into a list of its elements:
|
|
|
|
.. code-block:: nim
|
|
const
|
|
SymChars: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'}
|
|
|
|
proc classify(s: string) =
|
|
case s[0]
|
|
of SymChars, '_': echo "an identifier"
|
|
of '0'..'9': echo "a number"
|
|
else: echo "other"
|
|
|
|
# is equivalent to:
|
|
proc classify(s: string) =
|
|
case s[0]
|
|
of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_': echo "an identifier"
|
|
of '0'..'9': echo "a number"
|
|
else: echo "other"
|
|
|
|
|
|
When statement
|
|
--------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
|
|
when sizeof(int) == 2:
|
|
echo "running on a 16 bit system!"
|
|
elif sizeof(int) == 4:
|
|
echo "running on a 32 bit system!"
|
|
elif sizeof(int) == 8:
|
|
echo "running on a 64 bit system!"
|
|
else:
|
|
echo "cannot happen!"
|
|
|
|
The ``when`` statement is almost identical to the ``if`` statement with some
|
|
exceptions:
|
|
|
|
* Each condition (``expr``) has to be a constant expression (of type ``bool``).
|
|
* The statements do not open a new scope.
|
|
* The statements that belong to the expression that evaluated to true are
|
|
translated by the compiler, the other statements are not checked for
|
|
semantics! However, each condition is checked for semantics.
|
|
|
|
The ``when`` statement enables conditional compilation techniques. As
|
|
a special syntactic extension, the ``when`` construct is also available
|
|
within ``object`` definitions.
|
|
|
|
|
|
When nimvm statement
|
|
--------------------
|
|
|
|
``nimvm`` is a special symbol, that may be used as expression of ``when nimvm``
|
|
statement to differentiate execution path between runtime and compile time.
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
proc someProcThatMayRunInCompileTime(): bool =
|
|
when nimvm:
|
|
# This code runs in compile time
|
|
result = true
|
|
else:
|
|
# This code runs in runtime
|
|
result = false
|
|
const ctValue = someProcThatMayRunInCompileTime()
|
|
let rtValue = someProcThatMayRunInCompileTime()
|
|
assert(ctValue == true)
|
|
assert(rtValue == false)
|
|
|
|
``when nimvm`` statement must meet the following requirements:
|
|
|
|
* Its expression must always be ``nimvm``. More complex expressions are not
|
|
allowed.
|
|
* It must not contain ``elif`` branches.
|
|
* It must contain ``else`` branch.
|
|
* Code in branches must not affect semantics of the code that follows the
|
|
``when nimvm`` statement. E.g. it must not define symbols that are used in
|
|
the following code.
|
|
|
|
Return statement
|
|
----------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
return 40+2
|
|
|
|
The ``return`` statement ends the execution of the current procedure.
|
|
It is only allowed in procedures. If there is an ``expr``, this is syntactic
|
|
sugar for:
|
|
|
|
.. code-block:: nim
|
|
result = expr
|
|
return result
|
|
|
|
|
|
``return`` without an expression is a short notation for ``return result`` if
|
|
the proc has a return type. The `result`:idx: variable is always the return
|
|
value of the procedure. It is automatically declared by the compiler. As all
|
|
variables, ``result`` is initialized to (binary) zero:
|
|
|
|
.. code-block:: nim
|
|
proc returnZero(): int =
|
|
# implicitly returns 0
|
|
|
|
|
|
Yield statement
|
|
---------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
yield (1, 2, 3)
|
|
|
|
The ``yield`` statement is used instead of the ``return`` statement in
|
|
iterators. It is only valid in iterators. Execution is returned to the body
|
|
of the for loop that called the iterator. Yield does not end the iteration
|
|
process, but execution is passed back to the iterator if the next iteration
|
|
starts. See the section about iterators (`Iterators and the for statement`_)
|
|
for further information.
|
|
|
|
|
|
Block statement
|
|
---------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
var found = false
|
|
block myblock:
|
|
for i in 0..3:
|
|
for j in 0..3:
|
|
if a[j][i] == 7:
|
|
found = true
|
|
break myblock # leave the block, in this case both for-loops
|
|
echo found
|
|
|
|
The block statement is a means to group statements to a (named) ``block``.
|
|
Inside the block, the ``break`` statement is allowed to leave the block
|
|
immediately. A ``break`` statement can contain a name of a surrounding
|
|
block to specify which block is to leave.
|
|
|
|
|
|
Break statement
|
|
---------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
break
|
|
|
|
The ``break`` statement is used to leave a block immediately. If ``symbol``
|
|
is given, it is the name of the enclosing block that is to leave. If it is
|
|
absent, the innermost block is left.
|
|
|
|
|
|
While statement
|
|
---------------
|
|
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
echo "Please tell me your password:"
|
|
var pw = readLine(stdin)
|
|
while pw != "12345":
|
|
echo "Wrong password! Next try:"
|
|
pw = readLine(stdin)
|
|
|
|
|
|
The ``while`` statement is executed until the ``expr`` evaluates to false.
|
|
Endless loops are no error. ``while`` statements open an `implicit block`,
|
|
so that they can be left with a ``break`` statement.
|
|
|
|
|
|
Continue statement
|
|
------------------
|
|
|
|
A ``continue`` statement leads to the immediate next iteration of the
|
|
surrounding loop construct. It is only allowed within a loop. A continue
|
|
statement is syntactic sugar for a nested block:
|
|
|
|
.. code-block:: nim
|
|
while expr1:
|
|
stmt1
|
|
continue
|
|
stmt2
|
|
|
|
Is equivalent to:
|
|
|
|
.. code-block:: nim
|
|
while expr1:
|
|
block myBlockName:
|
|
stmt1
|
|
break myBlockName
|
|
stmt2
|
|
|
|
|
|
Assembler statement
|
|
-------------------
|
|
|
|
The direct embedding of assembler code into Nim code is supported
|
|
by the unsafe ``asm`` statement. Identifiers in the assembler code that refer to
|
|
Nim identifiers shall be enclosed in a special character which can be
|
|
specified in the statement's pragmas. The default special character is ``'`'``:
|
|
|
|
.. code-block:: nim
|
|
{.push stackTrace:off.}
|
|
proc addInt(a, b: int): int =
|
|
# a in eax, and b in edx
|
|
asm """
|
|
mov eax, `a`
|
|
add eax, `b`
|
|
jno theEnd
|
|
call `raiseOverflow`
|
|
theEnd:
|
|
"""
|
|
{.pop.}
|
|
|
|
If the GNU assembler is used, quotes and newlines are inserted automatically:
|
|
|
|
.. code-block:: nim
|
|
proc addInt(a, b: int): int =
|
|
asm """
|
|
addl %%ecx, %%eax
|
|
jno 1
|
|
call `raiseOverflow`
|
|
1:
|
|
:"=a"(`result`)
|
|
:"a"(`a`), "c"(`b`)
|
|
"""
|
|
|
|
Instead of:
|
|
|
|
.. code-block:: nim
|
|
proc addInt(a, b: int): int =
|
|
asm """
|
|
"addl %%ecx, %%eax\n"
|
|
"jno 1\n"
|
|
"call `raiseOverflow`\n"
|
|
"1: \n"
|
|
:"=a"(`result`)
|
|
:"a"(`a`), "c"(`b`)
|
|
"""
|
|
|
|
Using statement
|
|
---------------
|
|
|
|
The using statement provides syntactic convenience in modules where
|
|
the same parameter names and types are used over and over. Instead of:
|
|
|
|
.. code-block:: nim
|
|
proc foo(c: Context; n: Node) = ...
|
|
proc bar(c: Context; n: Node, counter: int) = ...
|
|
proc baz(c: Context; n: Node) = ...
|
|
|
|
One can tell the compiler about the convention that a parameter of
|
|
name ``c`` should default to type ``Context``, ``n`` should default to
|
|
``Node`` etc.:
|
|
|
|
.. code-block:: nim
|
|
using
|
|
c: Context
|
|
n: Node
|
|
counter: int
|
|
|
|
proc foo(c, n) = ...
|
|
proc bar(c, n, counter) = ...
|
|
proc baz(c, n) = ...
|
|
|
|
|
|
The ``using`` section uses the same indentation based grouping syntax as
|
|
a ``var`` or ``let`` section.
|
|
|
|
Note that ``using`` is not applied for ``template`` since untyped template
|
|
parameters default to the type ``system.untyped``.
|
|
|
|
|
|
If expression
|
|
-------------
|
|
|
|
An `if expression` is almost like an if statement, but it is an expression.
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
var y = if x > 8: 9 else: 10
|
|
|
|
An if expression always results in a value, so the ``else`` part is
|
|
required. ``Elif`` parts are also allowed.
|
|
|
|
When expression
|
|
---------------
|
|
|
|
Just like an `if expression`, but corresponding to the when statement.
|
|
|
|
Case expression
|
|
---------------
|
|
|
|
The `case expression` is again very similar to the case statement:
|
|
|
|
.. code-block:: nim
|
|
var favoriteFood = case animal
|
|
of "dog": "bones"
|
|
of "cat": "mice"
|
|
elif animal.endsWith"whale": "plankton"
|
|
else:
|
|
echo "I'm not sure what to serve, but everybody loves ice cream"
|
|
"ice cream"
|
|
|
|
As seen in the above example, the case expression can also introduce side
|
|
effects. When multiple statements are given for a branch, Nim will use
|
|
the last expression as the result value, much like in an `expr` template.
|
|
|
|
Table constructor
|
|
-----------------
|
|
|
|
A table constructor is syntactic sugar for an array constructor:
|
|
|
|
.. code-block:: nim
|
|
{"key1": "value1", "key2", "key3": "value2"}
|
|
|
|
# is the same as:
|
|
[("key1", "value1"), ("key2", "value2"), ("key3", "value2")]
|
|
|
|
|
|
The empty table can be written ``{:}`` (in contrast to the empty set
|
|
which is ``{}``) which is thus another way to write as the empty array
|
|
constructor ``[]``. This slightly unusual way of supporting tables
|
|
has lots of advantages:
|
|
|
|
* The order of the (key,value)-pairs is preserved, thus it is easy to
|
|
support ordered dicts with for example ``{key: val}.newOrderedTable``.
|
|
* A table literal can be put into a ``const`` section and the compiler
|
|
can easily put it into the executable's data section just like it can
|
|
for arrays and the generated data section requires a minimal amount
|
|
of memory.
|
|
* Every table implementation is treated equal syntactically.
|
|
* Apart from the minimal syntactic sugar the language core does not need to
|
|
know about tables.
|
|
|
|
|
|
Type conversions
|
|
----------------
|
|
Syntactically a `type conversion` is like a procedure call, but a
|
|
type name replaces the procedure name. A type conversion is always
|
|
safe in the sense that a failure to convert a type to another
|
|
results in an exception (if it cannot be determined statically).
|
|
|
|
Ordinary procs are often preferred over type conversions in Nim: For instance,
|
|
``$`` is the ``toString`` operator by convention and ``toFloat`` and ``toInt``
|
|
can be used to convert from floating point to integer or vice versa.
|
|
|
|
|
|
Type casts
|
|
----------
|
|
Example:
|
|
|
|
.. code-block:: nim
|
|
cast[int](x)
|
|
|
|
Type casts are a crude mechanism to interpret the bit pattern of
|
|
an expression as if it would be of another type. Type casts are
|
|
only needed for low-level programming and are inherently unsafe.
|
|
|
|
|
|
The addr operator
|
|
-----------------
|
|
The ``addr`` operator returns the address of an l-value. If the type of the
|
|
location is ``T``, the `addr` operator result is of the type ``ptr T``. An
|
|
address is always an untraced reference. Taking the address of an object that
|
|
resides on the stack is **unsafe**, as the pointer may live longer than the
|
|
object on the stack and can thus reference a non-existing object. One can get
|
|
the address of variables, but one can't use it on variables declared through
|
|
``let`` statements:
|
|
|
|
.. code-block:: nim
|
|
|
|
let t1 = "Hello"
|
|
var
|
|
t2 = t1
|
|
t3 : pointer = addr(t2)
|
|
echo repr(addr(t2))
|
|
# --> ref 0x7fff6b71b670 --> 0x10bb81050"Hello"
|
|
echo cast[ptr string](t3)[]
|
|
# --> Hello
|
|
# The following line doesn't compile:
|
|
echo repr(addr(t1))
|
|
# Error: expression has no address
|