General Nest syntax#
Comments#
In Nest there are two kinds of comments: line comments and block comments.
Line comments start with --
and end at the end of the line. If there is a
backslash at the end, the line feed is escaped and the line comment continues
to the next line.
Block comments start with -/
and end with /-
-- This is a single line comment
-- This is another single line comment \
that has the line-feed escaped
-/ this is a block comment which
spans multiple lines without
any escapes /-
File arguments#
In the command line there are some arguments that change the way a Nest file is
compiled and interpreted. You can add some of those argument specifying them
on the first line of the file. The line must start with --$
and then you can
have any combination of the following space-separated arguments:
-O0
through-O3
to specify the optimization level of the file; if the specified level is higher than the one specified by the actual command, the latter is used--encoding=<name>
forces the file to be read with the given encoding, the list of valid encodings can be found here--no-default
will not add any predefined variable to the global scope except for_vars_
In this example the file will be read using CP1252, only simple expressions are optimized and no predefined variables are added.
--$ --no-default --encoding=cp1252 -O1
Note
any invalid argument is ignored and when specifying various optimization levels only the last one is considered
Value literals#
Numeric literals#
Integer literals can be decimal, binary, octal or hexadecimal. The first one has
no prefix, the second 0b
, the third 0o
and the fourth 0x
.
Binary and octal literals cannot be directly followed by another integer and hexadecimal literals cannot be followed by a word.
Real number literals support scientific notation but must always have some digits both before and after the decimal point.
Both integer and real literals can be prefixed with a minus -
to make them
negative. There can also be a plus sign +
before the number but that does not
change its sign.
-- Integer literals
10
-123
012 -- equal to 12 but still valid
+11
0b101
0o377
0xab
0b102 -- invalid because followed by 2
0o159 -- invalid because followed by 9
0xabg -- invalid because followed by g
-- Real literals
0.2
-13.4
1.0
+38.1
1.2e10
1.2e-10
1. -- invalid
.3 -- also invalid
3e10 -- invalid because real literals must always contain the decimal point
Nest has also a byte type that can be written as an integer literal followed by
a lowercase b
or an uppercase one B
. If the integer is written with an
hexadecimal literal, since adding a b
at the end would be counted as digit,
you start with 0h
instead.
-- Byte literals
10b
256b -- equivalent to 0b
0b101b
0o123b
0hff
Decimal, binary and octal byte literals can be followed by a word or by another literal but that could cause confusion, so it is better to keep a space.
10bab -- split into '10b' and 'ab'
0b0b0 -- split into '0b' (from '0b0b') and '0'
00b10 -- split into '0b' (from '00b') and '10'
-- these are much clearer
10b ab
0b0b 0
00b 10
String literals#
String literals begin and end with either single or double quotes but there is a difference between the two: strings with double quotes can span multiple lines.
To escape a character you can use a backslash \
before the character and here
are all the valid escape sequences:
Sequence | Hex Value | Name |
---|---|---|
\\ |
5c |
Backslash |
\' |
27 |
Single quote |
\" |
22 |
Double quotes |
\a |
07 |
Alert / Bell |
\b |
08 |
Backspace |
\e |
1b |
Backspace |
\f |
0c |
Form feed / Page break |
\n |
0a |
Line feed / Newline |
\r |
0d |
Carriage return |
\t |
09 |
Horizontal tab |
\v |
0b |
Vertical tab |
\xhh |
- | Hexadecimal byte |
\ooo |
- | Octal byte |
\uhhhh |
- | Any BMP Unicode character |
\Uhhhhhh |
- | Any Unicode character |
\(...) |
- | A Nest expression |
Note
h
represents a hexadecimal byte (0-9
, a-f
or A-F
) and o
an
octal one (0-7
)
Note
Octal escapes can have either one, two or three digits: \0
, \12
and
\012
are all valid
Note
...
are to be substituted with the expression to evaluate
Array literals#
Array literals start and end with curly braces ({
and }
) and contain various
expressions separated by a comma.
{1, 2, 3, 4}
Note
writing {}
creates an empty map, not an empty array. To create an
empty array you can write {,}
You can also create an array with all of the same value like this:
{10;30} --> array of length 30, with all values that are 10
This puts the same object in all slots of the array meaning that if an object can change values inside of itself (array, vector or map) the values of all objects will change:
{{1, 2};2} = a --> 'a' is equal to {{1, 2}, {1, 2}}
3 = a.0 .0 --> 'a' is now equal to {{3, 2}, {3, 2}}
Vector literals#
Vector literals start with <{
and end with }>
and inside have various
expressions separated by a comma.
<{1, 2, 3, 4}>
Just like arrays, vectors also have a smaller syntax to fill all slots with the same value:
<{10;30}> --> vector of length 30, with all values that are 10
Map literals#
Map literals start with {
and end with }
and inside have key-value pairs.
{'key_1': 1, 'key_2': 2}
The keys can be of type Str
, Int
or Byte
since only these three types are
hashable. Real
objects cannot be hashed because of floating point error.
Anonymous functions (lambdas)#
Lambdas are declared by using ##
followed by any number of identifiers that
are the names of the parameters.
A lambda can return the value of a single expression by following the parameters
with =>
or contain multiple statements with a block of code, delimited by [
and ]
.
##a b => a b + --> this lambda adds 'a' and 'b', returning their value
-- This lambda also prints the operation
##a b [
>>> (a ' + ' b '\n' ><)
=> a b +
]
See also function declarations and the return statement.
Predefined variables#
Int
: integer typeReal
: real number typeBool
: boolean typeNull
: null typeStr
: string typeArray
: array type, non-resizable ordered collection of objectsVector
: vector type, resizable ordered collection of objectsMap
: hash map type, holds key-value pairsFunc
: function typeIter
: iterator typeByte
: byte type, an integer from 0 to 255IOFile
: file type, similar toFILE *
in CType
: type of all typestrue
: boolean truefalse
: boolean falsenull
: the only possible value of typeNull
_cwd_
: a string showing the current working directory_args_
: an array with the arguments passed in the command line_vars_
: a table containing the variables of the local scope_globals_
: a table containing the variables of the global scope
Note
the value of _globals_
in the global scope is null
Expressions#
Stack operators#
An expression in Nest works in a similar way as it does in Lisp, where there are some operators which can operate with an indeterminate number of operands; these are called stack operators.
The stack operators in Nest are:
+
addition-
subtraction*
multiplication/
division^
exponentiation%
modulus><
concatenation&&
logical and||
logical or&|
logical xor&
bit-wise and|
bit-wise or^^
bit-wise xor<<
bit-wise left shift>>
bit-wise right shift>
greater than>=
greater than or equal to<
less than<=
less than or equal to==
equal to!=
not equal to<.>
contains
To perform an operation with these operators you can write all the operands followed by the operator:
1 2 3 +
2 8 ^
To prioritize certain operations or to limit the number of operands of an operator, you can use parenthesis:
(3 2 ^) (4 2 ^) + 0.5 ^
this expression is equal to the python expression (3**2 + 4**2) ** 0.5
.
The comparison operators (<
, <=
, >
, >=
, ==
and !=
) operate in a
different way though. They check each pair of adjacent operands and then check
all the results to see if they are all true.
The following two expressions are equal but the first one is more concise:
1 2 3 4 <
(1 2 <) (2 3 <) (3 4 <) &&
Note
In the expression above, the values 2
and 3
are not actually evaluated
twice. If you had for example a function such as #f [>>> 'hi\n'[] => 2]
and used it inside the expression 1 @@f 2 <
, hi
would be only printed
once.
Note
Writing a b c !=
does not mean that all three values are different
from each other, it only means that adjacent ones are.
The operators +
, -
, *
and /
have a different meaning when operating in
vectors.
The +
operator appends any value to the end of a vector and returns the vector
itself.
The -
operator removes the first occurrence of a value in a vector and returns
the vector itself.
The *
operator repeats the contents of a vector a number of times and returns
the vector itself.
The /
operator pops a number of values from the end and returns the last value
popped.
>>> (<{1, 2, 3}> 2 + '\n' ><) --> <{1, 2, 3, 2}>
>>> (<{1, 2, 3}> 2 - '\n' ><) --> <{1, 3}>
>>> (<{1, 2, 3}> 2 * '\n' ><) --> <{1, 2, 3, 1, 2, 3}>
>>> (<{1, 2, 3}> 2 / '\n' ><) --> 2
The operator -
behaves differently with maps too. It will remove a key from a
map and return the map itself.
{'a': 1, 'b': 2} = m
>>> (m 'a' - '\n' ><) --> {'b': 2}
>>> (m 'j' - '\n' ><) --> {'b': 2} removing a key that does not exist does not
-- throw an error
Local operators#
In Nest there is another type of operator that is called local operator. A local operator only operates with the operand on the right.
The local operators in Nest are:
$
length of an object!
logical not~
bit-wise not>>>
write to the standard output<<<
write to the standard output and read from the standard input-:
negate?::
type of@@
local call, calls a function with no arguments|#|
import
To operate with these operators you can write the operator followed by its operand:
>>> 'Hello, world!\n'
This kind of operators have the highest precedence, this means that writing
>>> 'Hello ' name '!\n' ><
will be executed as (>>> 'Hello ') name '!\n' ><
,
to change the order you can use parenthesis: >>> ('Hello ' name '!\n' ><)
Local-stack operators#
This third type of operator in Nest can take a set number of arguments, depending on the operator you are using. These operators are used do to specific functions built in the language.
The local-stack operators are:
::
casts an object to another type@
calls a function*@
calls a function with an array or a vector containing the arguments->
creates an integer iterator!!
throws an error
To use a local-stack operator you write the last argument to the right and all the others to the left.
::
only takes two arguments: the object to cast and the type to cast it to.
Here the number 10
is casted to a Byte
object:
Byte :: 10
The valid type casts in Nest are the following:
↱ | Int |
Real |
Bool |
Null |
Str |
Array |
Vector |
Map |
Func |
Iter |
Byte |
IOFile |
Type |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Int |
✓ | ✓ | ✓ | ✓ | ✓ | ||||||||
Real |
✓ | ✓ | ✓ | ✓ | ✓ | ||||||||
Bool |
✓ | ✓ | |||||||||||
Null |
✓ | ✓ | ✓ | ||||||||||
Str |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||||
Array |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||||||
Vector |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||||||
Map |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||||||
Func |
✓ | ✓ | ✓ | ||||||||||
Iter |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||||||
Byte |
✓ | ✓ | ✓ | ✓ | ✓ | ||||||||
IOFile |
✓ | ✓ | ✓ | ||||||||||
Type |
✓ | ✓ | ✓ |
When Int
, Real
or Byte
objects are casted to Bool
, they become false
if they are zero and true
otherwise.
When Str
, Array
, Vector
or Map
objects are casted to a boolean, it
returns false
if their length zero and true
otherwise.
When an IOFile
is casted to Bool
, it returns true
if the file is open and
false
if it has been closed.
When Null
is casted to Bool
, it always returns false
.
When any other object is casted to a Bool
, it always returns true
.
@
takes the number of arguments of the function plus the function itself as
the last argument.
Here the function func
is called with three arguments:
1 2 3 @func
*@
calls a function as well but you can have the arguments inside a vector or
an array:
{1, 2, 3} *@func
Both for @
and *@
, if there are less arguments than the function expected,
to the remaining ones is passed null
.
->
takes two or three arguments, the first argument is optional and is the
step, the second is where the range should start, and the last is where the
range should end.
This creates a range of even numbers from 10 (included) to 20 (excluded):
2 10 -> 20
!!
takes two arguments, like ::
. The first one is the name of the error and
the last one the message that is printed along with the error.
Here is what would be printed when using this operator in example.nest
.
'This Is The Name' !! 'this is the message'
Output:
> nest example.nest
File "example.nest" at line 1:
1 | 'This Is The Name' !! 'this is the message'
This Is The Name - this is the message
If expression#
The syntax of the if expression is the following:
condition ?
-- condition is true
:
-- condition is false
The result of the if expression is the result of the expression evaluated, so
a b == ? 2 : 3
means that if a and b are equal, the expression evaluates to
2
and to 3
otherwise. When there is no 'else' clause, the expression
evaluates to null
.
If you want to execute multiple expressions or statements you can use brackets:
a b == ? [
-- code block here
] : [
-- other code block here
]
When using a code block the expression evaluates to null
:
(true ? [ true ] : false) = val
Here val
is set to null
because true
is inside a block.
Assignment expressions#
An assignment expression assigns a value to a variable or changes a value in a vector, map or array.
To write an assignment expression you write the value, followed by an equal sign
(=
) or a compound equal sign (+=
, -=
or any stack operator which is not a
comparison operator) and end with the name of the variable or an access to an
element in an array, vector or map.
10 = a -- assigns the value 10 to the variable 'a'
3 -= a -- decrements 'a' by 3, now it is 7
There is another kind of assignment in Nest: the unpacking assignment. An unpacking assignment takes a vector or an array and splits its contents into the variables.
{1, 2} = {a, b} --> now 'a' is 1 and 'b' is 2
It can also be nested:
{1, {2, 3}} = {a, {b, c}} --> now 'a' is 1, 'b' is 2 and 'c' is 3
And can be used in for-as loops:
|#| 'stditutil.nest' = itu
{'a', 'b', 'c'} = arr
... arr @itu.enumerate := {idx, ch} [
>>> (idx ' ' ch '\n' ><)
]
This program outputs:
1 a
2 b
3 c
Note
when using this assignment you cannot use compound assignments.
Access operator#
The access operator .
can be used to get a specific value from vectors,
arrays, strings and maps.
To access a value you write the value to access from, a dot, and the index of the value to get.
To index arrays, vectors or strings you can use integers. The first element is
at index 0
, the second at index 1
etc. until n - 1
where n
is the length
of the array or vector.
You can use also negative integers where -1
is the last element, -2
is the
second to last element etc.
{1, 2, 3} = arr
arr.0 --> 1
arr. -1 --> 3
If you need to index based on a variable you must use parenthesis otherwise the variable name is treated like a string.
{1, 2, 3} = arr
2 = idx
arr.idx --> ERROR: arr.idx is equal to arr.'idx'
arr.(idx) --> 3
To index multiple-dimension arrays or vectors you cannot chain multiple extractions since a real number literal would be formed. To prevent this you can either put parenthesis around the integer or put a space before the dot.
{{1, 2},
{3, 4}} = arr
arr.(0).(1) --> 2
arr.1 .1 --> 4
arr.1.0 --> ERROR: cannot index an array with '1.0'
To index a map you put the key of the value after the dot. If a key is a string that is also a valid variable name you can put the name without any quotes.
{'key_1': 2, 'invalid var': 10} = map
map.key_1 --> 10
map.invalid var --> ERROR: will try to get map.'invalid'
map.'invalid var' --> 10
map.not_a_key --> null
Note
if you try to index a key that is not in the map the result will be null
Statements#
The for loop#
The for loop has the following syntax:
... times_to_repeat [
-- code
]
The for loop only repeats a block of code a certain number of times, to use an iterator and assign a variable, use a for-as loop.
The for-as loop#
The for-as loop has the following syntax:
... iterator := var_name [
-- code
]
iterator
is an expression that evaluates to an iterator and var_name
is the
variable to be assigned.
This for loop prints the numbers one through ten:
... 1 -> 11 := i [
>>> (i '\n' ><)
]
The while and do-while loops#
The while loop has the following syntax:
?.. condition [
-- code
]
The do-while loop has the following syntax:
..? condition [
-- code
]
The only difference between while and do-while loops is that do-while loops execute the code once before checking the condition.
Function declaration#
A function declaration is a hash followed by the name of the function and the name of its arguments:
#func_name arg1 arg2 arg3 [
-- code
]
As with lambdas, functions can also contain only one expression and return its value:
#func_name arg1 arg2 => -/ expression /-
When calling a function the arguments are taken from left to right:
#print_args a b c [
>>> (a ' ' b ' ' c '\n' ><)
]
1 2 3 @print_args --> outputs '1 2 3'
The return statement#
The return statement is introduced by =>
and exits early from a function
returning the value of the expression that follows it.
If it is not followed by an expression or there is no such statement in the
function, null
is returned.
The switch statement#
The switch statement has the following syntax:
|> expression [
? case_1 [
-- code executed if expression is equal to case_1
]
? case_2 [
-- code executed if expression is equal to case_2
]
? [
-- code for the default case
]
]
If expression
is not equal to any of the cases, the code for the default case
is executed. If there is no default case, nothing happens.
When the code for a case is executed, the switch statement ends. To have the
same behaviour as C when the break
keyword is omitted, you can use the ..
keyword.
The following two codes are equivalent:
// C / C++
switch 10
{
case 5:
printf("5");
case 10:
printf("10");
default:
printf("default");
}
-- Nest
|> 10 [
? 5 [
>>> 5
..
]
? 10 [
>>> 10
..
]
? [
>>> 'default'
]
]
The try-catch statement#
The try-catch statement has the following syntax:
??
-- try block
?! error_var
-- catch block
If you want multiple statements you can use brackets:
?? [
-- try block
] ?! error_var [
-- catch block
]
When using a try-catch statement any error that occurs inside the try block will be caught in the catch block; you cannot catch only specific errors.
If an error occurs, it will be stored inside error_var
as a map with two keys:
name
: the name of the errormessage
: the message of the error
Note
you can call error_var
with any valid variable name
If you want to also get the position and traceback of the error you can use the
try
function of the stderr
library.