1
1
mirror of https://github.com/tweag/nickel.git synced 2024-10-06 16:18:08 +03:00
nickel/doc/manual/syntax.md

452 lines
11 KiB
Markdown
Raw Normal View History

2022-03-03 16:10:47 +03:00
---
slug: syntax
---
# Nickel Syntax
## Simple values
2022-03-03 16:10:47 +03:00
There are three basic kind of values in Nickel :
1. numeric values,
2. boolean values,
3. strings.
2022-03-03 16:10:47 +03:00
### Numeric values
Nickel has a support for numbers, positive and negative, with or without decimals.
Internally, those numbers are stored as 64-bits floating point numbers, following the IEEE 754 standard.
Examples:
```
1
0.543
42
-1000000
-6.8
```
There are a some predefined operators for working with numbers :
| Operator | Description | Example |
|:--------:|:----------------------------------------------------:|:-------------:|
| + | The addition operator | `1 + 2 = 3` |
| \- | The subtraction operator | `1 - 2 = -1` |
| * | The multiplication operator | `1 * 2 = 2` |
| / | The division operator | `1 / 2 = 0.5` |
| % | The modulo operator (returns the *signed* remainder) | `5 % 3 = 2` |
2022-03-03 16:10:47 +03:00
> **Remark about the `-` operator:**
> Since `-` can be used inside an identifier, the subtraction operators **needs** to be surrounded by spaces:
> write `1 - 1`, not `1-1`.
Numbers can be compared using the following operators :
| Operator | Description | Example |
|:--------:|:----------------:|:---------:|
| == | Equal | `5 == 5` |
| != | Not Equal | `5 != 4` |
| < | Smaller than | `2 < 3` |
| > | Greater than | `1 > -5` |
| >= | Greater or Equal | `1 >= 1` |
| <= | Smaller or Equal | `-1 <= 6` |
In the table below, you will find the operators sorted from highest to lowest precedence:
| Operators | Associativity | Remark |
|:--------------------:|:-------------:|-----------------------------------------------|
| `( ... )` | | parentheses always have the highest precedence |
| `-` | | unary negation (as in `-1`) |
| `*`, `/`, `%` | left-to-right | |
| `+`, `-` | left-to-right | binary addition and subtraction |
| `<`, `>`, `=<`, `>=` | left-to-right | |
| `==`, `!=` | left-to-right | |
### Boolean values
The boolean values in Nickel are denoted `true` and `false`.
Nickel features the classical boolean operators *AND* (&&), *OR* (||) and *NOT* (!).
The _AND_ and _OR_ operators are lazy in the evaluation of the second argument: for example, in `exp1 && exp2`, `exp2` is only evaluated if `exp1` evaluates to `false`.
Examples:
```
> true && false
false
> false || true
true
> ! true
false
```
### Strings
Nickel can work with sequences of characters, or strings.
2022-02-24 11:34:55 +03:00
Strings are enclosed by `" ... "` for a single line string or by `m%" ... "%m` for a multiline string.
They can be concatenated with the operator `++`.
Strings must be UTF-8 valid.
2022-02-24 11:34:55 +03:00
The string interpolation syntax is `"%{ < expression that evaluates to a string > }"`.
Examples:
```
> "Hello, World!"
"Hello, World!"
2022-02-24 11:34:55 +03:00
> m%"Well, if this isn't a multiline string?
Yes it is, indeed it is"%m
"Well, if this isn't a string?
Yes it is, indeed it is"
> "Hello" ++ "World"
"HelloWorld"
2022-02-24 11:34:55 +03:00
> let h = "Hello" in "%{h} World"
"Hello World"
2022-02-24 11:34:55 +03:00
> let n = 5 in "The number %{n}."
error: Type error
> let n = 5 in "The number %{string.from_num n}."
"The number 5."
```
Multiline strings are useful to write indented lines. The indentation is stripped from the beginning of the first line, and first and last lines are ignored if they are empty or contain only spaces.
Example:
```
2022-02-24 11:34:55 +03:00
> m%"
This line has no identation.
This line is indented.
This line is even more indented.
This line has no more identation.
2022-02-24 11:34:55 +03:00
"%m
"This line has no indentation.
This line is indented.
This line is even more indented.
2022-03-03 16:10:47 +03:00
This line has no more indentation."
```
The only special sequence in a multiline string is the string interpolation.
Examples:
```
2022-02-24 11:34:55 +03:00
> m%"Multiline\nString?"%m
"Multiline\nString?"
2022-02-24 11:34:55 +03:00
> m%"Multiline%{"\n"}String"%m
"Multiline
String"
```
2022-02-24 11:34:55 +03:00
A multiline string can be introduced and closed by multiple `%` signs, as long as this amount is equal. If you want to use string interpolation, you must use the same amount of `%` as in the delimiters.
Examples:
```
2022-02-24 11:34:55 +03:00
> m%%"Hello World"%%m
"Hello World"
2022-02-24 11:34:55 +03:00
> m%%%%%"Hello World"%%%%%m
"Hello World"
2022-02-24 11:34:55 +03:00
> let w = "World" in m%%"Hello %{w}"%%m
"Hello %{w}"
2022-02-24 11:34:55 +03:00
> let w = "World" in m%%"Hello %%{w}"%%m
"Hello World"
```
Multiline strings are "indentation-aware". This means that one could use an indented string interpolation and the indentation would behave as expected:
2022-02-23 13:32:10 +03:00
```
2022-02-24 11:34:55 +03:00
> let log = m%"
if log:
print("log:", s)
2022-02-24 11:34:55 +03:00
"%m in m%"
2022-02-23 13:32:10 +03:00
def concat(str_array, log=false):
res = []
2022-02-23 13:32:10 +03:00
for s in str_array:
2022-02-24 11:34:55 +03:00
%{log}
res.append(s)
return res
2022-02-24 11:34:55 +03:00
"%m
2022-02-23 13:32:10 +03:00
"def concat(str_array, log=false):
res = []
2022-02-23 13:32:10 +03:00
for s in str_array:
if log:
print("log:", s)
res.append(s)
return res"
```
## Equality
2022-01-17 16:04:22 +03:00
Operators `==` and `!=` are used to compare values. Two values of different types are never equal: that is, `==` doesn't perform implicit conversions.
2022-01-17 16:04:22 +03:00
Examples:
```
> 1 == 1
true
> 5 == 5.0
true
> "Hello" == "Hello"
true
> "Hello" != "World"
true
> 5 == "Hello"
false
> true == "true"
false
```
## Composite values
2022-03-03 16:10:47 +03:00
### Array
2022-02-23 13:32:10 +03:00
An array is a sequence of values. They are delimited by `[` and `]`, and elements are separated with `,`.
2022-01-17 16:08:31 +03:00
Examples:
```
[1, 2, 3]
["Hello", "World"]
[1, true, "true"]
[]
```
2022-02-23 13:32:10 +03:00
Arrays can be concatenated with the operator `@`:
2022-01-17 18:45:35 +03:00
```
> [1] @ [2, 3]
[ 1, 2, 3 ]
```
### Record
Records are key-value storage, or in Nickel terms, field-value storage. They are delimited by `{` and `}`, and elements are separated with `,`.
Field-value elements are noted as `field = value`.
The fields are strings, but can be written without quotes `"` if they respect identifiers syntax. Values can be of any type.
2022-01-17 18:45:35 +03:00
Elements inside a record are unordered.
Two records can be _merged_ together using the operator `&`. The reader can find more information about merging in the relevant documentation.
2022-01-17 18:45:35 +03:00
Examples:
```
{}
{a = 3}
{my_id_n5 = "my id number 5", "my id n4" = "my id number 4" }
2022-01-17 18:45:35 +03:00
{"5" = 5, six = 6}
```
Accessing a record field can be done using the `.` operator :
```
> { a = 1, b = 5 }.a
1
> { a = 1 }.b
error: Missing field
> { "1" = "one" }."1"
"one"
```
It is possible to write records of records via the *piecewise syntax*, where we separate fields by dots:
```
> { a = { b = 1 } }
{ a = { b = 1 } }
2022-03-03 16:10:47 +03:00
> { a.b = 1 }
{ a = { b = 1 } }
> { a.b = 1, a.c = 2, b = 3}
{ a = { b = 1, c = 2 }, b = 3 }
```
When fields are enclosed with double quotes (`"`), you can use string interpolation to create or access fields:
```
2022-02-24 11:34:55 +03:00
> let k = "a" in { "%{k}" = 1 }
{ a = 1 }
2022-02-24 11:34:55 +03:00
> let k = "a" in { a = 1 }."%{k}"
1
```
## Constructs
### If-Then-Else
This construct allows conditional branching in your code. You can use it as `if <bool expr> then <expr> else <expr>`.
Examples:
```
> if true then "TRUE :)" else "false :("
"TRUE :)"
> if false then "Not this one" else "This one"
"This one"
> if "forty-two" == 42 then "equal?" else "unequal"
"unequal"
> ["1"] @ (if 42 == "42" then ["3"] else ["2"]) @ ["3"]
["1", "2", "3"]
```
### Let-In
Let-in allows the binding of an expression. It is used as `let <ident> = <expr> in <expr>`.
Examples:
```
> let r = { a = "a", b = "b" } in r.a
"a"
> let inner = { inside = true } in let outer = { outside = inner.inside } in outer.outside
true
> let a = 1 in let b = 2 in a + b
3
```
## Functions
A function is declared using the `fun` keyword, then arguments separated with spaces, and finally an arrow `=>` to add the body of the function.
To call a function, just add the arguments after it separated with spaces.
Functions in Nickel are curried, meaning that a function taking multiple arguments is actually a function that takes a single argument and returns a function taking the rest of the arguments, until it is applied.
Examples:
```
> (fun a b => a + b) 1 2
3
let add = fun a b => a + b in add 1 2
3
> let add = fun a b => a + b in
let add1 = add 1 in
add1 2
3
```
All existing infix operators in Nickel can be turned into functions by putting them inside parentheses.
Examples:
```
> 1 + 2
3
> (+) 1 2
3
> let increment = fun n => (+) 1 n in
increment 41
42
> let increment = (+) 1 in
increment 41
42
2022-02-23 13:32:10 +03:00
> let flatten = array.fold (@) [] in flatten [[1, 2], [3], [4, 5]]
[ 1, 2, 3, 4, 5 ]
```
Functions might be composed using the *pipe operator*. The pipe operator allows for a function application `f x` to be written as `x |> f`.
This operator is left-associative, so `x |> f |> g` will be interpreted as `g (f x)`.
Examples:
```
> "Hello World" |> string.split " "
["Hello", "World"]
> "Hello World"
|> string.split " "
2022-02-23 13:32:10 +03:00
|> array.head
"Hello"
> "Hello World"
|> string.split " "
2022-02-23 13:32:10 +03:00
|> array.head
|> string.uppercase
"HELLO"
```
## Typing
To give a type to a value, we write it with `< value > : < type >`.
More information on typing in the relevant document.
Examples:
```
5 : Num
"Hello" : Str
(fun a b => a + b) : Num -> Num -> Num
let add : Num -> Num -> Num = fun a b => a + b
2022-02-23 13:32:10 +03:00
{a: Num = 1, b: Bool = true, c : Array Num = [ 1 ]}
let r : {a : Num, b : Bool, c : Array Num} = { a = 1, b = true, c = [ 1 ] }
{ a = 1, b = 2 } : { _ : Num }
let r : { _ : Num } = { a = 1, b = 2 }
```
2022-01-24 13:46:15 +03:00
## Metadata
Metadata are used to attach contracts (more information in relevant documentation), documentation or priority to values.
A metadata is introduced with the syntax `<value> | <metadata>`. Multiple metadata can be chained.
2022-01-24 13:46:15 +03:00
Examples:
```
> 5 | Num
5
> 5 | Bool
error: Blame error: contract broken by a value.
> let SmallNum = contract.from_predicate (fun x => x < 5) in
1 | SmallNum
2022-01-24 13:46:15 +03:00
1
> let SmallNum = contract.from_predicate (fun x => x < 5) in
10 | SmallNum
2022-01-24 13:46:15 +03:00
error: Blame error: contract broken by a value.
> let SmallNum = contract.from_predicate (fun x => x < 5) in
let NotTooSmallNum = contract.from_predicate (fun x => x >= 2) in
3 | Num
| SmallNum
| NotTooSmallNum
2022-01-24 13:46:15 +03:00
3
```
2022-03-03 16:10:47 +03:00
Adding documentation can be done with `| doc < string >`.
2022-01-24 13:46:15 +03:00
Examples:
```
> 5 | doc "The number five"
5
2022-02-24 11:34:55 +03:00
> true | Bool | doc m%"
2022-01-24 13:46:15 +03:00
If something is true,
it is based on facts rather than being invented or imagined,
and is accurate and reliable.
(Collins dictionary)
2022-02-24 11:34:55 +03:00
"%m
2022-01-24 13:46:15 +03:00
true
```
Record contracts can set default values using the `default` metadata:
It is noted as `| default = < default value >`.
2022-03-03 16:10:47 +03:00
This is especially useful when merging records (more about this in the dedicated document about merge).
2022-01-24 13:46:15 +03:00
Examples:
```
> let Ais2ByDefault = { a | default = 2 } in
{} | Ais2ByDefault
2022-01-24 13:46:15 +03:00
{ a = 2 }
> let Ais2ByDefault = { a | default = 2 } in
{ a = 1 } | Ais2ByDefault
2022-01-24 13:46:15 +03:00
{ a = 1 }
> { foo | default = 1, bar = foo + 1 }
{ foo = 1, bar = 2 }
> {foo | default = 1, bar = foo + 1} & {foo = 2}
{ foo = 2, bar = 3 }
2022-01-24 13:46:15 +03:00
```