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

556 lines
13 KiB
Markdown
Raw Normal View History

2022-03-03 16:10:47 +03:00
---
slug: syntax
---
# Nickel Syntax
2022-06-16 18:59:42 +03:00
## Identifiers
2022-08-02 16:41:43 +03:00
Nickel identifiers start with an alphabetic character, followed by zero or more
alphanumeric characters, `_` (underscores) or `'` (single quotes). For example,
`this-isn't-invalid` is a valid identifier.
2022-06-16 18:59:42 +03:00
## Simple values
There are four basic kind of values in Nickel :
2022-08-02 16:41:43 +03:00
1. numeric values
2. boolean values
3. strings
4. enum tags
2022-03-03 16:10:47 +03:00
### Numeric values
2022-08-02 16:41:43 +03:00
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:
2022-03-07 14:27:09 +03:00
```nickel
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-08-02 16:41:43 +03:00
> **Remark about the `-` operator:** Since `-` can be used inside an identifier,
> the subtraction operators **needs** to be surrounded by spaces: write `a - b`,
> not `a-b`. `1-2` works as expected, because `1` and `2` aren't identifiers.
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`.
2022-08-02 16:41:43 +03:00
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:
2022-08-02 16:41:43 +03:00
```text
> true && false
false
> false || true
true
> ! true
false
```
### Strings
2022-08-02 16:41:43 +03:00
Nickel can work with sequences of characters, or strings. 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-08-02 16:41:43 +03:00
The string interpolation syntax is
`"%{ < expression that evaluates to a string > }"`.
Examples:
2022-08-02 16:41:43 +03:00
```text
> "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."
```
2022-08-02 16:41:43 +03:00
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-08-02 16:41:43 +03:00
```text
2022-02-24 11:34:55 +03:00
> m%"
2022-05-18 04:21:10 +03:00
This line has no indentation.
This line is indented.
This line is even more indented.
2022-05-18 04:21:10 +03:00
This line has no more indentation.
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-08-02 16:41:43 +03:00
```text
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-08-02 16:41:43 +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-08-02 16:41:43 +03:00
```text
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"
```
2022-08-02 16:41:43 +03:00
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-08-02 16:41:43 +03:00
```text
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"
```
#### Enum tags
2022-08-02 16:41:43 +03:00
Enumeration tags are used to express finite alternatives. They are formed by
writing a backtick `` ` `` followed by any valid identifier. For example,
`builtin.serialize` takes an export format as a first argument, which is an enum
tag among `` `Json ``, `` `Toml `` or `` `Yaml `` (as of version 0.1):
```nickel
builtin.serialize `Json {foo = 1}
# gives "{
# \"foo\": 1
# }"
builtin.serialize `Toml {foo = 1}
# gives "foo = 1
# "
```
An enum tag `` `foo `` is serialized as the string `"foo"`:
```nickel
let as_yaml_string = builtin.serialize `Yaml {foo = `bar}
# gives "---
# foo: bar
# "
```
While it's technically possible to just use strings in place of enum tags, using
an enum tag insists on the fact that only a finite number of alternatives can be
used for the corresponding value.
Additionally, the typechecker is aware of enums and can for example statically
enforce that only valid tags are passed to a function within a typed block. See
[the manual section on typing](./typing.md) for more details.
## Equality
2022-01-17 16:04:22 +03:00
2022-08-02 16:41:43 +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:
2022-08-02 16:41:43 +03:00
```text
2022-01-17 16:04:22 +03:00
> 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-08-02 16:41:43 +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:
2022-08-02 16:41:43 +03:00
2022-03-07 14:27:09 +03:00
```nickel
2022-01-17 16:08:31 +03:00
[1, 2, 3]
["Hello", "World"]
[1, true, "true"]
[]
```
2022-02-23 13:32:10 +03:00
Arrays can be concatenated with the operator `@`:
2022-08-02 16:41:43 +03:00
```text
2022-01-17 18:45:35 +03:00
> [1] @ [2, 3]
[ 1, 2, 3 ]
```
### Record
2022-08-02 16:41:43 +03:00
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. 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 [section on merging](./merging.md).
2022-01-17 18:45:35 +03:00
Examples:
2022-08-02 16:41:43 +03:00
2022-03-07 14:27:09 +03:00
```nickel
2022-01-17 18:45:35 +03:00
{}
{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 :
2022-08-02 16:41:43 +03:00
```text
2022-01-17 18:45:35 +03:00
> { a = 1, b = 5 }.a
1
> { a = 1 }.b
error: Missing field
> { "1" = "one" }."1"
"one"
```
2022-08-02 16:41:43 +03:00
It is possible to write records of records via the *piecewise syntax*, where we
separate fields by dots:
```text
> { 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 }
```
2022-08-02 16:41:43 +03:00
When fields are enclosed with double quotes (`"`), you can use string
interpolation to create or access fields:
```text
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
2022-08-02 16:41:43 +03:00
This construct allows conditional branching in your code. You can use it as
`if <bool expr> then <expr> else <expr>`.
Examples:
2022-08-02 16:41:43 +03:00
```text
> 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
2022-08-02 16:41:43 +03:00
Let-in allows the binding of an expression. It is used as
`let <rec?> <ident> = <expr> in <expr>`. The `rec` keyword in Let-in constructs
allows the let binding to become recursive, enabling the use of the `<ident>`
within the first `<expr>`.
Examples:
2022-08-02 16:41:43 +03:00
```text
> 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
> let rec f = fun n => if n == 0 then n else n + f (n - 1) in f 10
55
> let rec fib = fun n => if n <= 2 then 1 else fib (n - 1) + fib (n - 2) in fib 9
34
2022-08-02 16:41:43 +03:00
> let rec repeat = fun n x => if n <= 0 then [] else repeat (n - 1) x @ [x] in
repeat 3 "foo"
["foo", "foo", "foo"]
```
## Functions
2022-08-02 16:41:43 +03:00
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:
2022-08-02 16:41:43 +03:00
```text
> (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
```
2022-08-02 16:41:43 +03:00
All existing infix operators in Nickel can be turned into functions by putting
them inside parentheses.
Examples:
2022-08-02 16:41:43 +03:00
```text
> 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 ]
```
2022-08-02 16:41:43 +03:00
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:
2022-08-02 16:41:43 +03:00
```text
> "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
2022-08-02 16:41:43 +03:00
To give a type to a value, we write it with `< value > : < type >`. More
information on typing in the relevant document.
Examples:
2022-08-02 16:41:43 +03:00
2022-03-07 14:27:09 +03:00
```nickel
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
2022-08-02 16:41:43 +03:00
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:
2022-08-02 16:41:43 +03:00
```text
2022-01-24 13:46:15 +03:00
> 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-08-02 16:41:43 +03:00
Adding documentation can be done with `| doc < string >`. Examples:
```text
2022-01-24 13:46:15 +03:00
> 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
```
2022-08-02 16:41:43 +03:00
Record contracts can set default values using the `default` metadata: It is
noted as `| default = < default value >`. 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:
2022-08-02 16:41:43 +03:00
```text
> 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
```