::: details Table of Contents
[[toc]]
:::
## Introduction
Navi (/ˈnævi/) is a high-performance programming and stream computing language developed in Rust, originally designed for complex and high-performance computing tasks. It is also suited as a glue language embedded within heterogeneous services in financial systems.
In addition to its capabilities as a statically typed, compiled language, Navi offers the convenience of script-like execution. It can compile source code into Bytecode (without JIT) or Machine Code (with JIT), providing a flexible development workflow. Theoretically, Navi delivers competitive performance on par with Go, Rust, and C.
### Language Design Philosophy
- **Simple and Clean Syntax**
Designed with a straightforward and clean syntax.
- **Modern Optional-Type and Error-Handling Design**
With a modern design of optional types and error handling, Navi allows developers to gracefully manage exceptional cases and abnormal data.
- **No NULL Pointer Panic, Safe Runtime**
No NULL pointer exceptions. Once your code passed compiles, you can expect consistent and reliable execution.
- **Scripted Execution**
Supports script-like execution, but offers the same performance comparable to compiled languages like Go.
### Functionalities
- **Dual-Domain Programming**
Serves as a dual-purpose language, functioning as both a general-purpose programming language and a domain-specific language optimized for incremental computation.
- **High Performance**
As a statically typed, compiled language, which is comparable to Go, Rust, and C.
- **Cross-platform**
Running on Linux, Windows, macOS, and through WebAssembly (WASM), it extends its reach to iOS, Android, and Web Browsers.
- **Native Cloud Support (WIP)**
With its standard library, Navi enables seamless manipulation of cloud computing resources as if they were local.
- **Native Financial Support (WIP)**
Navi is equipped with native support for incremental financial data computation, making it ideal for real-time calculation and analysis of stock market data.
It boasts a rich set of scientific computing capabilities, including built-in functions for technical stock market indicators, and standard library support for
LongPort OpenAPI, significantly reducing development costs for programmatic trading.
## Standard Library
The [Navi Standard Library](/stdlib/) has its own documentation.
## Getting Started
Write a `main.nv`, `.nv` is the file extension of the Navi language.
```nv
fn main() throws {
let name = "World";
let message = `Hello ${name}!\n`;
println(message);
}
```
Output:
```shell
$ navi run
Hello World!
```
> NOTE: If the file name is `main.nv` and it has the `main` function. The `navi run` will use it as the program entry.
> You also can execute with `navi run main.nv`.
This code sample demonstrates the basic syntax of Navi.
- The `use` keyword is used to import the `io` module from the standard library.
- The `//` is used to comment a line.
- The `fn` keyword is used to define a function.
- The `main` function is the entry point of the program, the `main` function must have the `throws` keyword, and it can throw an error.
- The `throws` keyword is used to declare a function that can throw an error.
- The `let` keyword is used to declare a variable.
- The `name` variable is a string type, or you can use `let name: string = "World";` to declare it.
- The `message` variable is defined by a string interpolation (Like JavaScript) by using "``", and the `${name}` is a variable reference.
- The `println` function is used to print a string to the console, the `println` and `print` function is default imported from the `std.io` module.
- Use `;` to end a statement.
- Finally, the Code style uses 4 spaces for indentation.\
## Comments
Navi supports 2 types of comments (Like Rust).
The `//` started is a normal comment, and it will be ignored by the compiler.
For example:
```nv,no_run
// This is a normal comment.
fn say(name: string): string {
// This is a normal comment.
// This is the second line of normal comment.
return `Hello ${name}!`;
}
```
There is no multi-line comment in Navi. If you want to write a multi-line comment, just use `//` for each line.
## Doc Comments
A doc comment is started with `///`, and it will be parsed by the compiler and generate documentation. You can write Markdown in it.
For example:
````nv,no_run
/// A struct doc comment.
struct User {
/// The user's name.
name: string,
}
impl User {
/// This is a doc comment for a function.
///
/// ## Args
///
/// - name: The name of the person to say hello to.
///
/// ```nv
/// let user = User { name: "Navi" };
/// assert_eq user.say(), "Hello Navi!";
/// ```
fn say(self): string {
return `Hello ${self.name}!`;
}
}
````
### Doctest
You can write Markdown Code Block in your doc comment, and use `navi test --doc` to run the doc tests.
Like regular tests, doc tests use the `assert`, `assert_eq`, and `assert_ne` keywords for assertion.
For example:
````nv,no_run
/// This is a doc comment for a function.
///
/// ```nv
/// let s = say("World");
/// assert_eq s, "Hello";
/// ```
fn say(name: string): string {
return `Hello ${name}!`;
}
````
Then you can run `navi test --doc` to run the doc test.
```shell
$ navi test --doc
test doc `say` . ok
thread 'main' at 'assertion failed: s == "Hello"', main:9
left: Hello World!
right: Hello
```
This will parse the code block in the doc comment and run it.
### Annotation for doctest
Code blocks can be annotated with attributes that help `navi test` do the right thing when testing your code:
- `ignore`: Ignore doc test (No compile and run).
- `should_panic`: This code should panic or assert failed.
- `no_run`: This code should pass compile but not run.
- `compile_fail`: This code block should fail to compile.
#### For example:
Expect to ignore (No compile and run)
````nv
/// ```nv,ignore
/// fn foo() {
/// ```
````
Expect to **panic** or **assert failed**
````nv
/// ```nv,should_panic
/// assert_eq 1 == 2;
/// ```
````
Expect to **pass compile** but **not run**
````nv
/// ```nv,no_run
/// loop { };
/// ```
````
Expect to **compile failed**
````nv
/// ```nv,compile_fail
/// a = 1
/// ```
````
## Values {#value}
### Primitive Types
| Type | Rust Equivalent | Description | Example |
|----------|-----------------|--------------------------|-------------------------------------------|
| [int] | i64 | A signed integer type | `1`, `-29`, `0` |
| [bool] | bool | A boolean type. | `true`, `false` |
| [float] | f64 | A floating point type | `1.0`, `-29.0`, `0.0` |
| [string] | str | A immutable UTF-8 string | `"Hello, 世界"`
`` `Hello ${1 + 2}` `` |
| [char] | char | A single character | `'a'`, `'b'`, `'c'` |
::: info
💡 Navi only has [int] and [float] types, all `int` are stored as _int64_, and all `float` are stored as _float64_ in internal.
There is no int8, uint8, int16, uint16, int32, uint32, float32, and etc.
:::
### Primitive Values
| Name | Description |
|--------------------|--------------------------------|
| `true` and `false` | [bool] values |
| `nil` | Set an [optional] value to nil |
### Integer {#int}
In Navi, the `int` type is a signed integer type, and it is 64-bit on all platforms. This means it can hold values from `-9223372036854775808` to `9223372036854775807`.
We don't have uint type or other integer types.
```nv
let n = 246;
let n1 = -100;
```
You can use `_` to separate digits in a number (`int`, `float`), it will be **ignored** by the compiler. This is useful for large numbers.
```nv
let amount = 1_000_000;
let price = 123_456_789.123_456;
```
### Float {#float}
Navi has a `float` type (53 bits of precision), and it is 64-bit on all platforms.
```nv
let v = 3.14;
let v1 = -2.0;
let v2 = 0.0;
let v3 = 10.23e+10;
let v4 = 2.0e+2;
```
### Bool {#bool}
Navi has a `bool` type, and it has two values: `true` and `false`.
```nv, no_run
let passed = true;
if (passed) {
println("Passed!");
} else {
println("Failed!");
}
let passed = false;
```
### Char {#char}
Navi has a `char` type, and it represents a single unicode scalar value.
```nv
let c1 = 'a';
let c2 = '🎉';
let s = "abc";
let c3 = s[1]; // 'b'
```
### String {#string}
Use double quotes (`""`) or backticks (` `` `) to create a `string` type.
In Navi all strings are **IMMUTABLE**, you can't change the value of a string.
```nv,no_run
fn main() throws {
let message = "Hello, World 🎉!";
println(message);
println(`chars len: ${message.len()}`);
println(`bytes len: ${message.bytes().len()}`);
}
```
Output:
```shell
$ navi run
Hello, World 🎉!
chars len: 15
bytes len: 18
```
#### Escape Sequences
| Escape Sequences | Description |
|------------------|-----------------|
| `\n` | Newline |
| `\r` | Carriage return |
| `\t` | Tab |
| `\\` | Backslash |
| `\"` | Double quote |
| `\'` | Single quote |
If you use `\` in a string outside of an escape sequence, it will be ignored.
```nv,no_run
fn main() throws {
println("\"Hello, \nWorld!\"");
println("Hello, \\nWorld!");
println("Unknown escape sequence: \a");
}
```
Output:
```shell
$ navi run
"Hello,
World!"
Hello, \nWorld!
Unknown escape sequence: a
```
### String Interpolation
String interpolation is a way to construct a new String value from a mix of constants, variables, literals, and expressions by including their values inside a [string] literal.
Navi's string interpolation is similar to JavaScript's template literals.
Use ` `` ` to create a string with interpolation, use `${}` to insert a value or expression.
```nv
let name = "World";
let hello = `Hello, ${name}!`;
// You can write multi-line string interpolation.
let hello = `
Hello, ${name}!
`;
```
#### ToString interface
The `ToString` interface is a built-in interface, and it has a `to_string` method, you can use it to convert a value to a string.
If you use any type that implements the `ToString` interface in string interpolation, it will call the `to_string` method to convert the value to a string.
For example:
```nv
struct User {
name: string
}
impl User {
pub fn to_string(self): string {
return self.name;
}
}
let user = User { name: "Navi" };
let message = `Hello, ${user}!`;
assert_eq message, "Hello, Navi!";
```
### Char
Navi has a `char` type, and it represents a single Unicode scalar value.
```nv
let c = 'a';
let c1: char = '🎉';
```
### Byte and Bytes
Navi not have byte type, but you can use `int` to represent a byte value, we can use `b''` to create a byte value from a char.
```nv
let b = b'a';
assert_eq b, 97;
```
Use `b""` to create a `Bytes` type from a string.
```nv
let bytes = b"Hello, World!";
// Now `bytes` is a `Bytes` type.
assert_eq bytes.len(), 13;
```
See also: [std.io.Bytes](/stdlib/std.io#std.io.Bytes)
### Assignment
Use the `let` keyword to declare a variable to an identifier, the variable is mutable.
```nv,no_run
// main.nv
let name = "World";
let pi = 3.14;
let passed = true;
fn main() throws {
name = "Navi";
pi = 3.1415926;
passed = false;
let message = `Hello ${name}, pi: ${pi}, passed: ${passed}!`;
println(message);
}
```
Output:
```shell
Hello Navi, pi: 3.1415926, passed: false!
```
You can declare a variable with a type.
```nv
let name: string = "World";
let pi: float = 3.14;
let passed: bool = true;
```
#### Destructuring Assignment
You can use destructuring assignment to assign structure fields to multiple variables.
```nv
struct Point {
x: int,
y: int,
}
let p = Point { x: 1, y: 2};
let Point { x, y } = p;
assert_eq x, 1;
assert_eq y, 2;
```
Nested structures can also be destructured.
```nv
struct Point {
x: int,
y: int,
}
struct Line {
start: Point,
end: Point,
}
let line = Line { start: Point { x: 1, y: 2 }, end: Point { x: 3, y: 4 } };
let Line { start: Point { x: x1, y: y1 }, end: Point { x: x2, y: y2 } } = line;
assert_eq x1, 1;
assert_eq y1, 2;
assert_eq x2, 3;
assert_eq y2, 4;
```
### Type Casting
Use `as` to cast a value to a type, this is **zero-cost** type casting.
You can cast a value from [int] to [float], or from [float] to [int].
| FROM | TO |
|-------|-------|
| int | float |
| float | int |
| bool | int |
```nv
test "cast" {
let n = 100 as float;
assert_eq n, 100.0;
let n = 3.1415 as int;
assert_eq n, 3;
let n = true as int;
assert_eq n, 1;
let n = false as int;
assert_eq n, 0;
}
```
The following are invalid type casting:
```nv,compile_fail
let n = 300 as string; // unable cast type `int` to `string`
let n = 3.1415 as string; // unable cast type `float` to `string`
let n = "10" as int; // unable cast type `string` to `int`
let n = "10" as float; // unable cast type `string` to `float`
let n = 0 as bool; // unable cast type `int` to `bool`
let n = true as float; // unable cast type `bool` to `float`
```
### Type Conversion
Use `parse_int`, and `parse_float` to convert an `string` to a `int` or `float`, the return value is an optional type. If the string value is invalid, it will return `nil`.
Use `to_string` to convert all [Primitive Types] to a `string`, this is always successful.
```nv
test "parse_int" {
let n = "100".parse_int();
assert_eq n, 100;
let n = "abc100".parse_int();
assert_eq n, nil;
let n = "100abc".parse_int();
assert_eq n, nil;
let n = 3.1415 as int;
assert_eq n, 3;
let n = true as int;
assert_eq n, 1;
let n = false as int;
assert_eq n, 0;
}
test "to_float" {
let n = "100".parse_float();
assert_eq n, 100.0;
let n = "3.1415".parse_float();
assert_eq n, 3.1415;
let n = "3.9abc".parse_float();
assert_eq n, nil;
let n = 3 as float;
assert_eq n, 3.0;
}
test "to_string" {
let n = 100.to_string();
assert_eq n, "100";
let n = 3.1415.to_string();
assert_eq n, "3.1415";
let n = true.to_string();
assert_eq n, "true";
let n = false.to_string();
assert_eq n, "false";
}
```
## Testing
You can use the `test` keyword to declare a test function in any Navi file, it will be run when you execute `navi test`.
There are built-in `assert`, `assert_eq`, and `assert_ne` keyword for assertion.
```nv
use std.io;
fn say(name: string): string {
return `Hello ${name}!`;
}
test "say" {
let message = say("World");
assert message == "Hello World!";
assert_eq message, "Hello World!";
assert_ne message, "";
}
```
Output:
```shell
$ navi test
test main.nv . ok
All 1 tests 1 passed finished in 0.03s
```
Like `navi run`, you can use `navi test main.nv` to run a specific file, if you don't pass a file name, it will run all files in the current directory (Like `navi test .`).
The code in the `test` block will be ignored by the compiler when you execute `navi run`.
### Test Declarations
You can use the `test` keyword to declare a test function, followed by a string literal as the test name, and then a block of code.
The `test` block can at anywhere in a Navi file, but it is recommended to put it at the end of the file.
```nv
use std.io;
fn say(name: string): string {
return `Hello ${name}!`;
}
// Here is ok
test "say" {
let message = say("World");
assert message == "Hello World!";
assert_eq message, "Hello World!";
assert_ne message, "";
}
fn add(a: int, b: int): int {
return a + b;
}
test "add" {
let result = add(1, 2);
assert result == 3;
assert_eq result, 3;
assert_ne result, 0;
}
```
Output:
```shell
$ navi test
test main.nv .. ok in 1ms
All 2 tests 2 passed finished in 0.02s.
```
### Test Failures
The test runner will print the error message when a test fails, and with an `exit 1` code to let CI know the test failed.
```nv
test "expect to fail" {
assert true == false;
}
test "expect to fail with message" {
assert_eq 1, 2, "1 != 2";
}
```
Output:
```shell
$ navi test
Testing
test main.nv .. fail in 708ms
main expect to fail
thread 'thread 1#' at 'assertion failed: true == false', main.nv:2
stack backtrace:
0: test#0()
at main.nv:2
main expect to fail with message
thread 'thread 1#' at '1 != 2', main.nv:6
left: 1
right: 2
stack backtrace:
0: test#1()
at main.nv:6
All 2 tests 0 passed, 2 failed finished in 0.79s.
```
### Track Caller
The `#[track_caller]` attribute can be used to mark a function as implicit caller location. When a function is marked with `#[track_caller]`, the panic call stack will not include this function.
This is useful when you want to hide the internal implementation details of a function from the panic call stack.
For example we have a custom assert function:
```nv
#[track_caller]
fn assert_success(value: bool) {
assert value == true;
}
test "assert_success" {
assert_success(true);
assert_success(false);
}
```
When the test faill, the panic call stack will not include the `assert_success` function:
```shell
error: thread 'thread 1#' at 'assertion failed: value == true', test.nv:8
stack backtrace:
0: test#0()
at test.nv:8
```
### Caller Location
We can use `call_location` method in `std.backtrace` module to get the caller, it will return a `CallerLocation` type that contains the file and line number of the caller.
And the `caller_locations` function can returns `[CallerLocation]` that contains a ordered list by call stack.
```nv
use std.backtrace;
fn assert_success(value: bool) {
if (!value) {
let caller = backtrace.caller_location()!;
panic `assertion failed on caller: ${caller.file}:${caller.line}`;
}
}
test "caller_location" {
assert_success(false);
}
```
Output:
```
caller_location
error: thread 'thread 1#' at 'assertion failed on caller:test.nv:4', test.nv:6
stack backtrace:
0: assert_success(bool)
at test.nv:6
1: test#0()
at test.nv:11
```
## Variable
### Declarations
The syntax of variable declarations is:
```rs
[] :[] =
```
where:
- `declaration_mode` - is the variable mode, we can use `let`, `cost`.
- `let` - declare a mutable variable.
- `const` - declare an immutable variable.
- `type` - used to declare the variable type, such as `int`, `string`, or a optional type `int?`, `string?`.
- `identifier` - variable name.
- `expression` - the value of the variable, can be any expression.
### Identifier
An identifier is a name used to identify a variable, function, struct, or any other user-defined item. An identifier starts with a letter or underscore `_`, followed by any number of letters, underscores, or digits.
It is recommended to use `snake_case` for identifiers, e.g. `my_var`, `my_function_name`.
They must not be a keyword. See [Keywords](#keywords) for a list of reserved keywords.
The following are valid identifiers:
```nv
const name = "World";
let _name = "World";
let name_ = "World";
let _name_ = "World";
let name1 = "World";
```
And they are invalid identifiers:
```nv,compile_fail
let 1name = "World";
let name-1 = "World";
// `use` is a keyword.
let use = "World";
```
### Variable Scope
Variables are scoped to the block in which they are declared. A block is a collection of statements enclosed by `{}`.
```nv,no_run
const name = "Name in global scope";
fn main() throws {
let name = "World";
println(`Hello ${name}!`);
foo();
}
fn foo() {
println(`Hello ${name}!`);
}
```
Output:
```shell
$ navi run
Hello World!
Hello Navi!
Hello World!
Hello Name in global scope!
```
### Const
The `const` keyword is used to declare an immutable variable, and it must have a value. When the value is assigned, it can't be changed.
```nv
const page_size = 200;
```
## Operator
Like other programming languages, Navi has a set of operators for performing arithmetic and logical operations.
| Operator | Relevant Types | Description | Example |
|------------|----------------|----------------------------------------------------------------------------------|--------------|
| `+` | [int], [float] | Addition | `1 + 2` |
| `+=` | [int], [float] | Addition | `a += 1` |
| `-` | [int], [float] | Subtraction | `1 - 2` |
| `-=` | [int], [float] | Subtraction | `a -= 1` |
| `*` | [int], [float] | Multiplication | `1 * 2` |
| `*=` | [int], [float] | Multiplication | `a *= 1` |
| `/` | [int], [float] | Division.
Can cause Division by Zero for integers. | `1 / 2` |
| `/=` | [int], [float] | Division | `a /= 1` |
| `%` | [int], [float] | Modulo | `1 % 2` |
| `%=` | [int], [float] | Modulo | `a %= 1` |
| `-a` | [int], [float] | Negation | `-1` |
| `a?.` | [optional] | Optional | `user?.name` |
| `a \|\| 1` | [optional] | Unwrap [optional] value or use default value.
| `a \|\| 0` |
| `a \|\| b` | [bool] | If `a` is `true`, returns `true` without evaluating `b`. Otherwise, returns `b`. | |
| `a && 1` | [bool] |
| `a!` | [optional] | Unwrap [optional] value or panic | `a!` |
| `a == b` | [int], [float], [bool], [string] ... | `a` equal to `b` | `1 == 2` |
| `a != b` | [int], [float], [bool], [string] ... | `a` not equal to `b` | `1 != 2` |
```nv
test "test" {
assert 1 + 2 == 3;
assert 1 - 2 == -1;
assert 1 * 2 == 2;
assert 1 / 2 == 0;
assert 1 % 2 == 1;
assert 1 == 1;
assert 1 != 2;
assert 1 < 2;
assert 1 <= 2;
assert 1 > 0;
assert 1 >= 0;
assert -1 == -1;
let a: string? = nil;
assert a?.len() == nil;
let a: string? = "Hello";
assert a?.len() == 5;
}
test "test assignment" {
let a = 1;
a += 1;
assert a == 2;
a -= 1;
assert a == 1;
a *= 2;
assert a == 2;
a /= 2;
assert a == 1;
let a = 10;
a %= 3;
assert a == 1;
}
```
Output:
```shell
$ navi test
Testing .
test main.nv .. ok in 1ms
All 2 tests 2 passed finished in 0.02s.
```
## Array
Array is a collection of items, in Navi array is a mutable collection.
Use `[]` to declare an array, every array must have a type, you can't create an array without a type.
The left side array type is optional, if array init with items, the type will be inferred.
```nv
struct Item {
name: string
}
test "array" {
let a = [1, 2, 3];
let b = ["Rust", "Navi"];
let c: [string] = ["Foo"];
assert a.len() == 3;
assert b.len() == 2;
// get array item
assert a[1] == 2;
assert b[0] == "Rust";
// set array item
a[1] = 3;
assert a[1] == 3;
// Init a struct array
let items: [Item] = [
{ name: "foo" },
{ name: "bar" },
{ name: "baz" }
];
assert_eq items[2].name, "baz";
}
```
If you init a empty array, you must declare the type.
```nv
let items: [string] = [];
let items: [int] = [];
```
Or you can use `[type; size]` to create an array use the same value.
For example, create a 10 length array with 0.
```nv
let items: [int] = [0; 10];
```
### Get & Set Item
Use `[idx]`, `[idx]=` to get and set an item from the array, the index must be an [int] type.
```nv,should_panic
let a = ["Rust", "Navi"];
a[0]; // "Rust"
a[1]; // "Navi"
a[2]; // panic: index out of bounds
a[0] = "Rust 1";
a[0]; // "Rust 1"
```
### Mutate Array
There are `push`, `pop`, `shift`, `unshift` ... methods in Array, you can use them to mutate an array.
```nv
test "push | pop" {
let items: [string] = [];
items.push("foo");
items.push("bar");
assert_eq items.len(), 2;
assert_eq items[0], "foo";
assert_eq items[1], "bar";
assert_eq items.pop(), "bar";
assert_eq items.pop(), "foo";
assert_eq items.pop(), nil;
}
test "shift | unshift" {
let items: [string] = [];
items.unshift("foo");
items.unshift("bar");
assert_eq items.len(), 2;
assert_eq items, ["bar", "foo"];
assert_eq items.shift(), "bar";
assert_eq items.len(), 1;
assert_eq items.shift(), "foo";
assert_eq items.len(), 0;
assert_eq items.shift(), nil;
let items = ["foo", "bar"];
assert_eq items.shift(), "foo";
assert_eq items.len(), 1;
assert_eq items.shift(), "bar";
assert_eq items.len(), 0;
assert_eq items.shift(), nil;
}
```
### Nested Array
The array can be nested.
```nv
let items: [[string]] = [
["foo", "bar"],
["baz", "qux"],
];
let numbers = [[1, 2], [3, 4]];
```
## Map
Map is a collection of key-value pairs, in Navi map is a mutable collection.
Use `{:}` to declare a map, every map must have a type, you can't create a map without a type.
You use use any built-in type as a key, and any type as a value.
```nv
let a: = {"name": "Navi", "version": "0.1.0"};
let b: = {1: 1.0, 2: 2.0};
```
If you init a empty map, you must declare the type, otherwise the type will be inferred.
```nv
let a: = {:};
let a: = {:};
let c = {1: "foo", 2: "bar"};
let d = {"name": "Navi", "version": "0.1.0"};
```
### Get & Set Item
Use `[key]`, `[key]=` to get and set an item from the map, the key must be a type that can be compared.
```nv
let items = {"name": "Navi", "version": "0.1.0"};
assert_eq items["name"], "Navi";
assert_eq items["version"], "0.1.0";
assert_eq items.len(), 2;
assert_eq items.keys(), ["name", "version"];
assert_eq items.values(), ["Navi", "0.1.0"];
items["name"] = "Navi 1";
assert_eq items["name"], "Navi 1";
```
## Struct
The Navi struct is a collection of fields, and it is a [value] type, it's like a struct in Go and Rust.
### Declare a Struct
Use the `struct` keyword to declare a struct, and use `.` to access a field.
- The struct name must be an [identifier] with `CamelCase` style, e.g.: `User`, `UserGroup`, `UserGroupItem`.
- And the field name must be an [identifier], with `snake_case` style, e.g.: `user_name`, `user_group`, `user_group_item`.
- The filed type can be a type or an [optional] type.
- The field can have a default value, e.g.: `confirmed: bool = false`, and then you can create a struct instance without the `confirmed` field.
```nv
struct User {
name: string,
id: int,
profile: Profile?,
// default value is `false`
confirmed: bool = false,
}
struct Profile {
bio: string?,
city: string?,
tags: [string] = [],
}
```
To create a struct instance, use `StructName { field: value }` syntax.
If the variable name is the same as the field name, you can assign it in short syntax, e.g.: `name` is the same as `name: name`.
```nv,ignore
let name = "Jason Lee";
let id = 100;
let user = User { id, name }; // This is same like `User { id: id, name: name }`
```
::: info
In the current version, you must assign `nil` to an [optional] field if you don't want to set a [value].
We will support [optional] field default value to `nil` in the future.
:::
```nv
test "user" {
let user = User {
name: "Jason Lee",
id: 1,
profile: {
bio: nil,
city: "Chengdu",
},
};
assert_eq user.name, "Jason Lee";
assert_eq user.confirmed, false;
assert_eq user.profile?.bio, nil;
assert_eq user.profile?.city, "Chengdu";
assert_eq user.tags.len(), 0;
}
```
### Implement a Struct
Use `impl` to declare a struct method. The `self` is a keyword, it is a reference to the current struct instance.
Unlike Rust, you don't need to declare `self` as the first parameter.
Use `impl .. for` to implement a interface for a struct. This is a optional way for let us write a clearly code, if the struct have a method can matched the interface, it same as the `impl .. for` implementation.
```nv, ignore
impl User {
fn new(name: string): User {
return User {
name: name,
id: 0,
profile: nil,
};
}
fn say(self): string {
return `Hello ${self.name}!`;
}
}
impl ToString for User {
fn to_string(self): string {
return self.name;
}
}
```
- `new` is a **Static Method**, and it can be called by `User.new`.
- `say` is an **Instance Method**, and it can be called by `user.say()`.
```nv, ignore
fn main() throws {
let user = User.new("Sunli");
println(user.say());
}
```
### Struct Attributes
Use `#[serde(attr = ...)]` to declare a struct serialize and deserialize attributes.
#### `#[serde(rename_all = "...")]`
Rename all the fields (if this is a struct) or variants (if this is an enum) according to the given case convention. The possible values are `"lowercase"`, `"UPPERCASE"`, `"PascalCase"`, `"camelCase"`, `"snake_case"`, `"SCREAMING_SNAKE_CASE"`, `"kebab-case"`, `"SCREAMING-KEBAB-CASE"`.
```nv
#[serde(rename_all = "camelCase")]
struct User {
user_name: string,
user_group: string,
}
```
Output:
```json
{
"userName": "Sunli",
"userGroup": "Admin"
}
```
#### `#[serde(deny_unknown_fields)]`
Always error during deserialization when encountering unknown fields. When this attribute is not present, by default unknown fields are ignored for self-describing formats like JSON.
::: info NOTE
This attribute is not supported in combination with `flatten`, neither on the outer struct nor on the flattened field.
:::
```nv
#[serde(deny_unknown_fields)]
struct User {
user_name: string,
user_group: string,
}
```
If we have a source JSON like this:
```json
{
"user_name": "Sunli",
"user_group": "Admin",
"unknown_field": "unknown"
}
```
In this case, we still can deserialize the JSON to a struct, but the `unknown_field` will be ignored.
```nv,ignore
use std.json;
let user = json.parse::(`{ "user_name": "Sunli", "user_group": "Admin", "unknown_field": "unknown" }`);
assert_eq user.user_name, "Sunli";
```
### Field Attributes
#### `#[serde(rename = "...")]`
Serialize and deserialize this field with the given name instead of its Rust name. This is useful for serializing fields as camelCase or serializing fields with names that are reserved Rust keywords.
```nv
struct User {
#[serde(rename = "name")]
user_name: string,
#[serde(rename = "team")]
user_group: string,
}
```
Output:
```json
{
"name": "Sunli",
"team": "Admin"
}
```
#### `#[serde(alias = "name")]`
Deserialize this field from the given name or from its Navi name. May be repeated to specify multiple possible names for the same field.
```nv
struct User {
#[serde(alias = "name")]
user_name: string,
#[serde(alias = "team")]
user_group: string,
}
```
So both JSONs are valid:
```json
{
"name": "Sunli",
"team": "Admin"
}
```
```json
{
"user_name": "Sunli",
"user_group": "Admin"
}
```
#### `#[serde(skip)]`
Skip this field: do not serialize or deserialize it. The `skip` field must have a default value, otherwise, it will cause a compile error.
```nv
struct User {
name: string,
#[serde(skip)]
group: string = "Other",
}
```
Output:
```json
{
"name": "Sunli"
}
```
And also can deserialize the JSON without the `group` field.
```nv, ignore
use std.json;
let user = json.parse::(`{ "name": "Sunli", "group": "Admin" }`);
assert_eq user.name, "Sunli";
// The `group` field is described with `skip`, so it will be ignored, we still get that default value.
assert_eq user.group, "Other";
```
#### `#[serde(flatten)]`
Flatten the contents of this field into the container it is defined in.
This removes one level of structure between the serialized representation and the Rust data structure representation. It can be used for factoring common keys into a shared structure, or for capturing remaining fields into a map with arbitrary string keys.
::: info NOTE
This attribute is not supported in combination with structs that use `deny_unknown_fields`. Neither the outer nor inner flattened struct should use that attribute.
:::
##### Flatten a struct fields
In some cases, we want to flatten a struct field to the parent struct. So we can use `#[serde(flatten)]` to do that.
```nv
struct User {
name: string,
#[serde(flatten)]
profile: Profile,
}
struct Profile {
bio: string,
city: string,
}
```
Now all fields in the `Profile` struct will be flattened to the `User` struct after serialization.
```json
{
"name": "Sunli",
"bio": "Hello, World!",
"city": "Wuhan"
}
```
##### Capture additional fields
A field of map type can be flattened to hold additional data that is not captured by any other fields of the struct.
```nv
struct User {
name: string,
#[serde(flatten)]
extra: ,
}
```
For example, we have a lot of unknown fields in the JSON, the all unknown fields will be captured to the `extra` field.
```json
{
"name": "Sunli",
"bio": "Hello, World!",
"city": "Wuhan"
}
```
```nv, ignore
use std.json;
let user = json.parse::(`{ "name": "Sunli", "bio": "Hello, World!", "city": "Wuhan" }`);
assert_eq user.name, "Sunli";
assert_eq user.extra["bio"], "Hello, World!";
assert_eq user.extra["city"], "Wuhan";
```
## Enum
The Navi `enum` is a collection of variants, and it is a [value] type.
### Declare an Enum
Use `enum` keyword to declare an enum, and use `.` to access a variant. Enum only be an [int] type.
```nv
enum UserRole {
Admin,
User,
Guest,
}
```
The first variant will be `0`, the second variant will be `1`, and so on in order.
```nv
enum UserRole {
Admin = 100,
User = 101,
Guest = 103,
}
```
### Convert to a value
Use `as` to convert an enum to a value, this is zero-cost.
```nv, ignore
let a = UserRole.Admin as int;
assert_eq a, 100;
```
### Omit Name
Enum literals can omit the type when the type can be inferred.
```nv,no_run
enum Color {
Red,
Green,
Blue,
}
let color: Color = .Red;
fn value(color: Color): int {
return 0;
}
value(.Red); // same as `value(Color.Red)`
```
### Enum Annotations
Like the struct, `enum` also has annotations for declaring serialize and deserialize attributes.
#### Enum Attributes
##### `#[serde(int)]`
Serialize and deserialize this enum as an integer.
```nv
#[serde(int)]
enum UserRole {
Admin,
User,
Guest,
}
struct User {
role: UserRole,
}
```
If present, the serialized representation of the enum will be an integer.
```json
{
"role": 100
}
```
Otherwise will use the enum field name as the serialized representation.
```json
{
"role": "Admin"
}
```
##### `#[serde(rename_all = "...")]`
This is the same as the struct `#[serde(rename_all = "...")]`. See [Struct Attributes](#struct-attributes).
Please note that the `rename_all` attribute is not supported in combination with `#[serde(int)]`.
#### Enum Item Attributes
The enum item also has annotations for declaring serialize and deserialize attributes.
##### `#[serde(rename = "...")]`
This is the same as the struct `#[serde(rename = "...")]`. See [Struct Attributes](#struct-attributes).
##### `#[serde(alias = "...")]`
This is the same as the struct `#[serde(alias = "...")]`. See [Struct Attributes](#struct-attributes).
## Interface {#interface}
The Navi interface is a collection of methods, and it is a [value] type, it's like an interface in Go.
### Declare an Interface
Use the `interface` keyword to declare an interface, and use `.` to access a method.
- The interface name must be an [identifier] with `CamelCase` style, we recommend named interface use a verb, e.g.: `ToString`, `Read`, `Write`.
- And the method name must be an [identifier], with `snake_case` style, e.g.: `to_string`, `read`, `write`.
- We can write a default implementation for a method, and it will be used if the struct does not implement the method.
- The first argument of the method must be `self`, it is a reference to the current struct instance.
```nv, ignore
interface ToString {
pub fn to_string(self): string;
}
interface Read {
fn read(self): string;
fn read_all(self): string {
// This is the default implementation, if the struct does not implement this method, it will be used.
return "";
}
}
fn read_all(reader: Read): ToString {
let s = reader.read();
// Navi's string has a `to_string` method.
return s;
}
```
### Implement an Interface
If any struct has all methods of an interface, it will implement the interface.
```nv, ignore
interface ToString {
fn to_string(self): string;
}
interface Reader {
fn read(self): string;
}
struct User {
name: string
}
impl User {
fn to_string(self): string {
return `${self.name}`;
}
fn read(self): string {
return `Hello ${self.name}!`;
}
}
```
Now we can use a `User` type as a `ToString` or a `Read` interface.
```nv, ignore
fn foo(item: ToString) {
println(item.to_string());
}
fn read_info(item: Reader) {
println(item.read());
}
fn main() throws {
let user = User {
name: "Sunli",
};
foo(user);
read_info(user);
}
```
You can also specify the specific interface type to be implemented to ensure that the interface methods are implemented correctly.
```nv, ignore
impl User for ToString {
fn to_string(self): string {
return `${self.name}`;
}
}
```
### Default Implementation
If the struct does not implement a method, the default implementation will be used.
```nv
interface I {
fn print(self) {
println("I");
}
}
```
### Convert to its parent interface
If a struct implements a child interface, it will also implement the parent interface.
```nv
interface A {
fn a(self): string;
}
interface B: A {
fn b(self): string;
}
struct C {}
// C implements A and B.
impl C {
fn a(self): string {
return "A";
}
fn b(self): string {
return "B";
}
}
let c = C {};
let b: B = c;
let a: A = b; // implicit convert B to A.
```
### Type Assertion
Use `.(type)` to assert an interface to a type.
```nv, compile_fail
interface ToString {
fn to_string(): string;
}
struct User {
}
impl User {
fn to_string(): string {
return "User";
}
}
let a: ToString = "hello";
// Cast a from interface to string.
let b = a.(string);
// now b is a `string`.
let user: ToString = User {};
// Cast user from interface to User.
let user = user.(User);
// now use is `User`.
let user = user.(string); // panic: User can't cast to a string.
```
#### Optional Type Assertion
Use `x?.(type)` to assert an optional interface to a type, if `x` is `nil`, the result will be `nil`.
```nv
let a: Any? = 10;
assert_eq a?.(int), 10;
```
## Switch
The `switch` statement is used to execute one of many blocks of code.
```nv,no_run
fn get_message(n: int): string {
let message = "";
switch (n) {
case 1:
message = "One";
case 2:
message = "Two";
case 3, 4:
message = "Three or Four";
default:
message = "Other";
}
return message;
}
fn main() throws {
println(get_message(1));
println(get_message(2));
println(get_message(3));
println(get_message(4));
println(get_message(5));
}
```
Output:
```shell
$ navi run
One
Two
Three or Four
Three or Four
Other
```
Use the `switch` keyword to declare a switch statement, the condition must have `()` and return a value. And use `case` and `default` to declare a case.
The `default` case is optional, which means if the condition does not match any case, it will execute the `default` case.
You can also use `{}` to declare a [block] in case of more complex logic.
```nv
fn get_message(n: int): string {
let message = "";
switch (n) {
case 1:
message = "One";
case 2:
message = "Two";
default:
message = "Other";
}
return message;
}
```
### Type switch
The `switch` can also used to assert the dynamic type of an interface variable. Use `let t = val.(type)` to assert the type of `val` in the switch condition.
The `Any` type is a special type that can hold any type of value.
```nv
fn type_name(val: Any): string {
switch (let t = val.(type)) {
case int:
return "int";
case string:
return "string";
case float:
return "float";
case bool:
return "bool";
case :
return "map";
default:
return "unknown";
}
}
test "type_name" {
assert_eq type_name(1), "int";
assert_eq type_name("hello"), "string";
assert_eq type_name(3.14), "float";
assert_eq type_name(true), "bool";
assert_eq type_name({"foo": 1}), "map";
assert_eq type_name(["foo"]), "unknown";
}
```
## While
A while loop is used to repeatedly execute an expression until some condition is no longer true.
Use the `while` keyword to declare a while loop, the condition is an [expression] in `()` that returns a [bool] value.
```nv,no_run
fn main() throws {
let n = 0;
while (n < 5) {
println(`${n}`);
n += 1;
}
}
```
Output:
```shell
$ navi run
0
1
2
3
4
```
Use the `break` keyword to exit a while loop.
```nv,no_run
fn main() throws {
let n = 0;
while (true) {
println(`${n}`);
n += 1;
if (n == 2) {
break;
}
}
}
```
Output:
```shell
$ navi run
0
1
```
Use `continue` to jump back to the beginning of the loop.
```nv,no_run
fn main() throws {
let n = 0;
while (n < 5) {
n += 1;
if (n % 2 == 0) {
continue;
}
println(`${n}`);
}
}
```
Output:
```shell
$ navi run
1
3
5
```
### While Let
The `while let` statement can be used to match an [optional] type, it will execute the block if the value is not `nil`, otherwise will exit the loop.
```nv,ignore
fn read_line(): string? {
// ..
}
while (let line = read_line()) {
println(line);
}
```
## For
For loops are used to iterate over a range, an array, or a map.
Like `while` loop, you can use `break` and `continue` to control the loop.
### Iter a Range {#range}
The range `start..end` contains all values with `start <= x < end`. It is empty if `start >= end`.
The `for (let i in start..end)` statement is used to iterate over a `std.range.Range`.
```nv,no_run
fn main() throws {
for (let n in 0..5) {
if (n % 2 == 0) {
continue;
}
println(`n: ${n}`);
}
}
```
Output:
```shell
$ navi run
n: 1
n: 3
```
Use the `step` method to create a new range with a step.
```nv
let items: [int] = [];
for (let i in (1..10).step(2)) {
items.push(i);
}
assert_eq items, [1, 3, 5, 7, 9];
```
### Iter an Array
The `for (let item in array)` statement is used to iterate over an [array].
```nv,no_run
fn main() throws {
let items = ["foo", "bar", "baz"];
for (let item in items) {
println(item);
}
}
```
Output:
```shell
$ navi run
foo
bar
baz
```
### Iter a Map
The `for (let k, v in map)` statement is used to iterate over a [map].
```nv
let items = {
"title": "Navi",
"url": "https://navi-lang.org"
};
let result: [string] = [];
for (let k, v in items) {
result.push(`${k}: ${v}`);
}
assert_eq result.join(", "), "title: Navi, url: https://navi-lang.org";
```
Output:
```shell
$ navi run
title: Navi
url: https://navi-lang.org
```
### Custom Iteration
You can implement the `iter` method for a struct to customize the iteration.
```nv
struct A {
count: int
}
impl A {
pub fn iter(self): AIter {
return AIter { i: 0, end: self.count };
}
}
struct AIter {
i: int,
end: int
}
impl AIter {
pub fn next(self): int? {
if (self.i < self.end) {
self.i += 1;
return self.i;
} else {
return nil;
}
}
}
let r: [int] = [];
for (let i in A { count: 10 }) {
r.push_back(i);
}
assert_eq r, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
```
## If
Like most programming languages, Navi has the `if` statement for conditional execution.
```nv,no_run
fn main() throws {
let n = 1;
if (n == 1) {
println("One");
} else if (n == 2) {
println("Two");
} else if (n == 3) {
println("Three");
} else {
println("Other");
}
}
```
### If Let
The `if let` statement can be used to match an [optional] type or [interface] type.
Match an [optional] type.
```nv,no_run
fn print_value(a: string?) {
if (let a = a) {
println(a);
} else {
println("a is nil");
}
}
print_value("Hello"); // Hello
print_value(nil);// a is nil
```
Match an [interface] type.
```nv,no_run
struct User {
name: string
}
fn print_value(value: Any) {
if (let user = value.(User)) {
println(user.name);
} else {
println("any");
}
}
print_value(User { name: "Jason Lee" }); // Jason Lee
print_value(123); // any
print_value("Hello"); // any
```
## Function
Use `fn` keyword to declare a function, the function name must be an [identifier], and the function body must be a [block].
::: info
Navi recommends using `snake_case` for the function name, e.g.: `send_message`, `get_user`, `get_user_by_id`.
And the argument name also uses `snake_case`, e.g.: `title`, `user_id`.
:::
You can define a function at the module level, or in a struct `impl` block.
- The function name must be an [identifier].
- The arguments can be [normal arguments], [keyword arguments] or [arbitrary arguments].
```nv,no_run
fn add(a: int, b: int, args: ..string, mode: string = "+"): string {
let result = a + b;
return `${a} + ${b} = ${result}`;
}
struct User {
name: string,
}
impl User {
fn say(self): string {
return `Hello ${self.name}!`;
}
}
fn main() throws {
println(add(1, 2));
let user = User { name: "Navi" };
println(user.say());
}
```
Output:
```shell
$ navi run
a + b = 3
Hello Navi!
```
### Positional Arguments
Positional arguments are arguments that are passed by position.
Use `name: type` to declare a positional argument, you can put a positional argument after the keyword argument.
```nv
fn add(a: int, b: int, mode: string = "+"): string {
let result = a + b;
return `${a} + ${b} = ${result}`;
}
```
To define an [optional] type for an argument, we use `?` after the type, e.g.: `b: int?`.
```nv,no_run
fn add(a: int, b: int?): string {
// unwrap b or default to 0
let b = b || 0;
let result = a + b;
return `${a} + ${b} = ${result}`;
}
fn main() throws {
println(add(1, 2));
println(add(1, nil));
}
```
Output:
```shell
$ navi run
1 + 2 = 3
1 + 0 = 1
```
### Arbitrary Arguments
Use `..type` to declare an arbitrary argument, the arbitrary argument must be the **last** argument (Except Keyword Arguments).
This is means you can pass any number of arguments to the function. And this argument will be as an array in the function body.
```nv
fn add(one: int, others: ..int): int {
// The `others` is `[int]` type.
let result = one;
for (let n in others) {
result += n;
}
return result;
}
assert_eq add(1, 2, 3, 4, 5), 15;
let others = [2, 3, 4, 5];
assert_eq add(1, ..others), 15;
```
### Keyword Arguments
Keyword arguments (Kw Args) are arguments that are passed by name. They are useful when a function has many arguments or default arguments.
Use `name: value = default` to declare a keyword argument, the keyword argument must be after positional arguments.
```nv,no_run
fn add(a: int, b: int, mode: string = "+", debug: bool = false): string {
if (debug) {
return `a: ${a}, b: ${b}, mode: ${mode}`;
}
let result: int = 0;
if (mode == "-") {
result = a + b;
} else {
result = a - b;
}
return `${a} ${mode} ${b} = ${result}`;
}
fn main() throws {
println(add(1, 2));
println(add(1, 2, mode: "+"));
println(add(1, 2, mode: "-"));
println(add(1, 2, mode: "-", debug: true));
println(add(1, 2, debug: true, mode: "+"));
println(add(1, 2, debug: true));
}
```
Output:
```shell
$ navi run
1 + 2 = -1
1 + 2 = -1
1 - 2 = 3
a: 1, b: 2, mode: -
a: 1, b: 2, mode: +
a: 1, b: 2, mode: +
```
### Function to a Variable
In Navi, the Function is the first-class citizen, it can be assigned to a variable, and it can be passed as an argument to another function.
```nv,no_run
use std.io;
fn add(a: int, b: int): string {
return `${a + b}`;
}
struct User {
name: string,
}
impl User {
fn say(self): string {
return `Hello ${self.name}!`;
}
}
fn main() throws {
let add_fn = add;
println(add_fn(1, 2));
let user = User { name: "Navi" };
let say_fn = user.say;
println(say_fn());
}
```
## Closure
A closure is a function that captures the environment in which it was created. It can capture variables from the surrounding scope.
- The closure type just use `|(type, type): return_type|`.
- If there is no parameter, use `|(): return_type|`.
- If no return type, use `|(type, type)|`.
- If no parameter and return type, use `|()|`.
- If the closure may throw an error, use `throws` keyword before the return type, e.g.: `|(int, int): throws string|`.
```nv
fn call_add(f: |(int, int): int|): int {
return f(2, 3);
}
let add: |(int, int): int| = |a, b| {
return a + b;
};
assert_eq add(1, 2), 3;
assert_eq call_add(add), 5;
fn call_add1(f: |(): int|): int {
return f() + 2;
}
assert_eq call_add1(|| {
return 3;
}), 5;
fn call_add2(f: |()|): int {
f();
return 2;
}
assert_eq call_add2(|| {
// do something without return
}), 2;
```
### Method Closure
The method closure is a closure that captures the struct instance.
```nv
struct User {
name: string,
}
impl User {
fn say(self): string {
return `Hello ${self.name}!`;
}
}
fn call_say(f: |(): string|): string {
return f();
}
let user = User { name: "Navi" };
assert_eq call_say(user.say), "Hello Navi!";
```
## Optional
Navi provides a modern optional type, which is similar to Rust's `Option` type, to give us a safe way to handle `nil` value, we can avoid the `null pointer exception` in runtime.
Use `type?` to declare an optional type, e.g.: `string?`, `int?`, `float?`, `bool?`, `User?` ...
```nv
// a normal string
let name: string = "Navi";
// an optional string
let optional_name: string? = "Navi";
let optional_name: string? = nil;
```
Now the `optional_name` is an [optional] type, it can be a [string] or `nil`.
### Unwrap Optional
The `!` operator is used to unwrap an [optional] value, if the value is `nil`, it will panic.
It is useful when you want to get a [value] from an [optional] value and you are sure it is not `nil`.
::: warning NOTE
To keep your code safe, when you use `!`, you must be sure it is not `nil`.
If not, don't use it, the [value || default](#unwrap-or-default) is a better way to get a [value] from an [optional] value.
:::
```nv,no_run
fn main() throws {
let name: string? = "Navi";
// This is ok.
println(name!);
let name: string? = nil;
// This will cause a panic.
println(name!);
}
```
### Unwrap or Default
The `||` operator is used to unwrap an [optional] value, if the value is `nil`, it will return the default value.
The right side of `||` can be a [value] or an [expression] that returns a [value], if the left side is not nill, the right side will not be evaluated.
```nv
use std.io;
test "unwrap or default" {
let name: string? = "Navi";
let result = name || "";
// result is a string type
assert_eq result, "Navi";
let name: string? = nil;
let result = name || "";
// result is a string type
assert_eq result, "";
}
```
### More methods
We also provide some methods to handle the [optional] value, such as `map`, `and`, `and_then`, `is_nil`, `map_or`, `unwrap_or`, `expect`, `unwrap_or_else`.
See also: [Optional Methods](/stdlib/lang.optional).
```nv,no_run
// a normal string
let name: string = "Navi";
// an optional string
let optional_name: string? = "Navi";
let optional_name: string? = nil;
fn main() throws {
let name: string? = "Navi";
// This is ok.
println(name!);
// This is also ok.
println(name.unwrap());
println(name.expect("Name is nil"));
println(name.map(|name| {
return `Name length: ${name.len()}`;
}));
println(name.and("And other name"));
println(name.and_then(|name| {
return `And then name: ${name}`;
}));
let name: string? = nil;
println(`name is nil: ${name.is_nil()}`);
println(name.map_or("Default value", |name| name.len()));
println(name.unwrap_or("unwrap_or a default value"));
println(name.or("Or a default value"));
println(name.or_else(|| "Or else a default value"));
// This will cause a panic.
println(name!);
}
test "unwrap or default" {
let name: string? = "Navi";
let result = name || "";
// result is a string type
assert_eq result, "Navi";
let name: string? = nil;
let result = name || "";
// result is a string type
assert_eq result, "";
}
```
### Let Else
The `let else` statement is used to handle the `nil` value.
```nv,no_run
fn print_value(value: string?) {
let value = value else {
println("value is nil");
return;
};
println(value);
}
print_value("Navi"); // Navi
print_value(nil); // value is nil
```
## Error
The `throws` keyword on a function to describe that the function can be thrown an error.
::: warning NOTE
All functions whose signature is `throws` must use `try`, `try?` or `try!` keyword before it when you call it.
:::
| Keyword | Description |
|-----------|--------------------------------------------------------------|
| `throws` | The function can throw an error. |
| `try` | The error will be thrown, if the function throws an error. |
| `try?` | If error is thrown, the `try?` will return `nil`. |
| `try!` | If error is thrown, the `try!` will panic. |
| `throw` | Throw an error. |
| `do` | The `do` block is used to handle an error. |
| `catch` | The `catch` block is used to match an error interface. |
| `finally` | The `finally` block is optional, it will always be executed. |
| `panic` | The `panic` function is used to panic the program. |
### Error Interface
By default, `throw` can throw with a [string] or a custom error type that implements the `Error` interface.
::: info TIP
Because Navi has implemented the `Error` interface for [string], you can throw a [string] directly.
:::
```nv
pub interface Error {
fn error(self): string;
}
```
So you can just throw [string]:
```nv, ignore
throw "error message";
```
Or implement the `Error` interface for a custom error type:
```nv
struct MyError {
message: string
}
impl Error for MyError {
// Implement the `error` method for `MyError` struct, then `MyError` can be used as an `Error` interface.
pub fn error(self): string {
return self.message;
}
}
```
We can use `throws` to declare an error type or keep it empty to use default error.
```nv, ignore
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
fn hello_with_custom_error(name: string): string throws MyError {
if (name == "Navi") {
throw MyError { message: "name can't be Navi" };
}
return `Hello ${name}!`;
}
```
For example:
```nv,no_run
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
fn main() throws {
let result = try? hello("Navi");
println(`${result || ""}`);
}
```
### Catch Error
Use `do ... catch` statement to catch an error.
- In the `do` block, you must use `try` keyword before all functions that can throw an error.
- The `catch` block is used to match an error interface, it can have multiple `catch` blocks to match different error types.
- And the `finally` block is optional, it will always be executed.
Every type that implements the `error` method can be used as an `Error` interface.
```nv,ignore
use std.io;
struct MyError {
message: string
}
impl Error for MyError {
// Implement the `error` method for `MyError` struct, then `MyError` can be used as an error interface.
pub fn error(): string {
return self.message;
}
}
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
do {
let result = try hello("Navi");
println(result);
let result1 = try hello("Sunli");
} catch (e) {
println(e.error());
} catch (e: MyError) {
// ...
} finally {
// This block always be executed.
}
```
### Handle Error
#### try
If the function throws an error, the `try` will throw the error.
```nv, no_run
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
fn main() throws {
let result = try hello("Navi");
// if error is thrown, the `try` will throw the error.
}
```
##### Error conversion
The `try` statement can convert one error into another.
```nv, ignore
type MyError2 = MyError1;
fn throws_error1() throws MyError1 {
}
fn a() throws Error2 {
try throws_error1(); // This will convert MyError1 to MyError2
}
```
#### try?
If the function throws an error, the `try?` will return `nil`.
```nv, ignore
let result = try? hello("Navi");
assert_eq result, nil;
let result = try? hello("Sunli");
assert_eq result, "Hello Sunli!";
```
#### try!
If the function throws an error, the `try!` will panic.
```nv, ignore
let result = try! hello("Navi");
// This will cause a panic
```
### Panic
The `panic` keyword is used to panic the program.
```nv, ignore
fn hello(name: string): string {
if (name == "Navi") {
panic "name can't be Navi";
}
return `Hello ${name}!`;
}
```
When `panic` is called, the program will stop running and print the error message.
## Use
The `use` keyword is used to import a module from the standard library or a file.
```nv
use std.io;
use std.url.Url;
fn main() throws {
let url = try Url.parse("https://navi-lang.org");
assert_eq url.host(), "navi-lang.org";
}
```
When you import, the last part of the module name is the name of the module, e.g.: `use std.io` to `io`, `std.url.URL` to `URL`, `std.net.http` to `http`.
### Alias
Sometimes we may want to use a different name for a module, we can use `as` to import a module with an alias.
```nv
use std.url.Url as BaseUrl;
let url = try! BaseUrl.parse("https://navi-lang.org");
assert_eq url.host(), "navi-lang.org";
```
### Use multiple modules
We can use multiple modules by one `use`.
```nv
use std.{io, url.Url};
fn main() throws {
let url = try Url.parse("https://navi-lang.org");
assert_eq url.host(), "navi-lang.org";
}
```
### Import a Module from local
In Navi, a folder in the current directory is a module, and the module name is the folder name.
For example, we have a struct:
```shell
$ tree
main.nv
models
|── profile
| |── a.nv
| └── b.nv
└── user.nv
utils
|── string.nv
└── url.nv
```
Now you can import them in `main.nv`:
```nv,ignore
use models;
use models.profile;
use utils;
```
### Module Variables
You also can import a variable from a module.
```nv
use std.net.http.NotFound;
let status = NotFound;
```
## Module System
In Navi a folder in the current directory is a module, and the module name is the folder name.
- The root directory is the `main` module, and uses `main.nv` as the entry file by default.
- The any sub-directory as a sub-module, and `use` the directory name as the module name.
- The root directory can have multiple entry files, and you can use `navi run filename.nv` to run it directly.
- The `pub` keyword is used to export a `struct`, `struct field`, `interface`, `function`, `type`, `enum`, `const`, `let`, then the other modules can use it.
For example, we have a project like this:
```shell
$ tree
main.nv
utils.nv
models
|── user_profile.nv
|── user_profile
| |── profile_a.nv
| └── profile_b.nv
config
|── config_a.nv
└── config_b.nv
```
In this case:
- `main.nv`, `utils.nv` files are in the `main` module, they can access and share members with each other.
- `models` directory is a module named `models`.
- `models/user_profile.nv` will be compiled into `models` module.
- `models/user_profile` directory is a module named `models.user_profile`.
- `modles/user_profile/*.nv` files will be compiled into `models.user_profile` module, they are same like one file.
- `config` directory is a module named `config`.
- `config/*.nv` files will be compiled to the `config` module, they are the same as one file.
::: warning NOTE
If your project has multiple sub-modules, you need to link them by `use` keyword to let the Navi compiler know the module dependency.
Only the used modules will be compiled, this means `navi test` or other commands will not find the sub-directory modules if you don't use them.
:::
For example, in `main.nv`:
```nv, ignore
use models;
use config;
fn main() throws {
}
```
## Type
We have `type` and `type alias` in Navi to create a type based on an existing type.
- `type` is used to create a new type, user can not see the original type, and the new type not have any method of the original type.
And we can use `as` the convert to the original type with zero cost.
- `type alias` is used to create a new name for an existing type, the new name is acutally the same as the original type.
### New Type
Use `type` keyword to create a newtype based on an existing type, when define as a new type, the original type will not be seen.
The rules of the new type:
- New type can base on any type.
- The behavior of the original type will not be seen (init way, methods, etc)
- Unlike define a Struct, wrap a new type is zero cost.
- We can use `as` to convert to the original type, or convert from the original type to the new type.
- We can use `impl` to add methods to the new type.
```nv
type MyString = string;
impl MyString {
fn as_string(self): string {
return self as string;
}
pub fn len(self): int {
return self.as_string().len();
}
}
let s = "hello" as MyString;
assert_eq s.len(), 5;
// This will fall, because MyString is a new type, it only have `len` method by the below impl.
// s.to_uppercase()
let s1 = s as string;
// now we can use the string method
assert_eq s1.to_uppercase(), "HELLO";
```
### Type Alias
Use `type alias` to create a new name for an existing type.
Unlike the [new type], the type alias is **just a new name** for the original type, so we can use the original type's method directly.
```nv
type alias Key = string;
type alias Value = int;
// We can assign a string to Key type, because Key is a string alias, use is same as string.
let key = "test";
assert_eq key.len(), 4;
type alias MyInfo = ;
let info: MyInfo = {
"foo": 1,
"bar": 2,
};
assert_eq info["foo"], 1;
assert_eq info["bar"], 2;
```
## Union Type
The union type allows us to combine two or more types into one type.
```nv
fn to_string(val: int | string | float): string {
switch (let val = val.(type)) {
case int:
return `int: ${val}`;
case float:
return `float: ${val}`;
case string:
return `string: ${val}`;
}
}
assert_eq to_string(1), "int: 1";
assert_eq to_string(3.14), "float: 3.14";
assert_eq to_string("hello"), "string: hello";
```
It also can be used as a struct field type.
```nv
struct User {
stuff_number: int | string,
}
let user = User {
stuff_number: 1,
};
let user = User {
stuff_number: "one",
};
```
Or with return type.
```nv
fn get_stuff_number(): (int | string) {
return 1;
}
```
## Defer
The `defer` keyword is used to execute a block of code when the current function returns.
This is most like Go's `defer` keyword. It is useful when you want to do some cleanup work, e.g.: close a file, close a database connection, etc.
```nv, no_run
use std.io;
fn main() throws {
defer {
println("defer 1");
}
defer {
println("defer 2");
}
println("Hello");
}
```
Output:
```shell
$ navi run
Hello
defer 2
defer 1
```
## Spawn
Navi has a `spawn` keyword for spawn a coroutine, it is similar to Go's `go` keyword.
```nv
use std.time;
fn main() throws {
let ch: channel = channel();
spawn {
println("This will print 1");
time.sleep(0.1.seconds());
println("This is print from spawn 1");
// Signal that we're done
try! ch.send(1);
}
spawn {
println("This will print 2");
time.sleep(0.1.seconds());
println("This is print from spawn 2");
// Signal that we're done
try! ch.send(1);
}
println("This is printed 3");
// Wait for the spawned task to finish
try ch.recv();
println("All done");
}
```
Unlike Go, Navi is a single-thread language, so the `spawn` is to make code run concurrently, not parallelly.

> Graph from [Concurrency is NOT Parallelism]
See also:
- [Concurrency is NOT Parallelism]
## Channel
The `channel` is a communication mechanism that allows one goroutine to send values to another goroutine.
Use `channel` to create a channel, and use `send` to send a value to the channel, and use `recv` to receive a value from the channel.
```nv
let ch = channel::();
spawn {
let i = 1;
while (i <= 10) {
try! ch.send(i);
i += 1;
}
}
let i = 1;
while (i <= 10) {
let value = try! ch.recv();
assert value == i;
i += 1;
}
```
## Keywords
The following are reserved keywords in Navi, they can't be used as [identifier].
| Keyword | Description |
|-----------------------------|--------------------------------------------------------------------------------------------|
| `as` | Convert a value to a type. |
| `assert_eq` | assert equal |
| `assert_ne` | assert not equal |
| `assert` | assert |
| `bench` | Benchmark function |
| `benches` | Benchmark group |
| `break` | `break` is used to exit a loop before iteration completes naturally. |
| `case` | `case` for the `switch` statement. |
| `catch` | Use `catch` to catch an error. |
| `const` | Declare a constant. |
| `continue` | `continue` can be used in a loop to jump back to the beginning of the loop. |
| `default` | `default` case for `switch` statement. |
| `defer` | Execute a block of code when the current function returns. |
| `do` | Use `do` to handle an error. |
| `else` | `else` can be used to provide an alternate branch for [if], [switch], [while] expressions. |
| `enum` | Define an enum. |
| `false` | false |
| `finally` | Use `finally` to execute a block of code after `try` and `catch` blocks. |
| `fn` | Declare a function. |
| `for` | [for] loop |
| `if` | [if] statement |
| `impl` | Declare a struct implementation. |
| `in` | key use in [for] loop |
| `interface` | Define a [interface] |
| `let` | Declare a variable. |
| `loop` | an infinite [loop] |
| `nil` | An [optional] value of nil. |
| `panic` | Panic an error. |
| `pub` | Mark a function, struct, interface or enum as public. |
| `return` | Return a value from a function. |
| `select` | Use to select a [channel]. |
| `self` | A reference to the current struct instance. |
| `spawn` | [Spawn] a coroutine. |
| `struct` | Define a struct. |
| `switch` | [switch] statement |
| `test` | Test function |
| `tests` | Test group |
| `throw` | Throw an error. |
| `throws` | Declare a function can throw an [error]. |
| `true` | true |
| `try`
`try?`
`try!` | Use `try` to handle an error. |
| `type` | Create a type alias. |
| `use` | [use] a module from the standard library or a file. |
| `while` | [while] loop |
[Primitive Types]: #primitive-types
[Use]: #use
[Intergers]: #int
[int]: #int
[string]: #string
[Floats]: #float
[float]: #float
[char]: #chars
[optional]: #optional
[bool]: #bool
[String interpolation]: #string-interpolation
[identifier]: #identifier
[identifiers]: #identifier
[values]: #value
[value]: #value
[block]: #block
[expression]: #expression
[Positional Arguments arguments]: #positional-arguments
[Keyword Arguments]: #keyword-arguments
[Kw Argument]: #keyword-arguments
[Arbitrary Arguments]: #arbitrary-arguments
[while]: #while
[loop]: #loop
[for]: #for
[switch]: #switch
[if]: #if
[channel]: #channel
[spawn]: #spawn
[unwrap || default]: #unwrap-or-default
[Unwrap or Default]: #unwrap-or-default
[Concurrency is NOT Parallelism]: https://ics.uci.edu/~rickl/courses/ics-h197/2014-fq-h197/talk-Wu-Concurrency-is-NOT-parallelism.pdf
[Error]: #error
[Type Alias]: #type-alias
[Defer]: #defer
[new type]: #new-type
---
::: details Table of Contents
[[toc]]
:::
## Introduction
Navi (/ˈnævi/) is a high-performance programming and stream computing language developed in Rust, originally designed for complex and high-performance computing tasks. It is also suited as a glue language embedded within heterogeneous services in financial systems.
In addition to its capabilities as a statically typed, compiled language, Navi offers the convenience of script-like execution. It can compile source code into Bytecode (without JIT) or Machine Code (with JIT), providing a flexible development workflow. Theoretically, Navi delivers competitive performance on par with Go, Rust, and C.
### Language Design Philosophy
- **Simple and Clean Syntax**
Designed with a straightforward and clean syntax.
- **Modern Optional-Type and Error-Handling Design**
With a modern design of optional types and error handling, Navi allows developers to gracefully manage exceptional cases and abnormal data.
- **No NULL Pointer Panic, Safe Runtime**
No NULL pointer exceptions. Once your code passed compiles, you can expect consistent and reliable execution.
- **Scripted Execution**
Supports script-like execution, but offers the same performance comparable to compiled languages like Go.
### Functionalities
- **Dual-Domain Programming**
Serves as a dual-purpose language, functioning as both a general-purpose programming language and a domain-specific language optimized for incremental computation.
- **High Performance**
As a statically typed, compiled language, which is comparable to Go, Rust, and C.
- **Cross-platform**
Running on Linux, Windows, macOS, and through WebAssembly (WASM), it extends its reach to iOS, Android, and Web Browsers.
- **Native Cloud Support (WIP)**
With its standard library, Navi enables seamless manipulation of cloud computing resources as if they were local.
- **Native Financial Support (WIP)**
Navi is equipped with native support for incremental financial data computation, making it ideal for real-time calculation and analysis of stock market data.
It boasts a rich set of scientific computing capabilities, including built-in functions for technical stock market indicators, and standard library support for
LongPort OpenAPI, significantly reducing development costs for programmatic trading.
## Standard Library
The [Navi Standard Library](/stdlib/) has its own documentation.
## Getting Started
Write a `main.nv`, `.nv` is the file extension of the Navi language.
```nv
fn main() throws {
let name = "World";
let message = `Hello ${name}!\n`;
println(message);
}
```
Output:
```shell
$ navi run
Hello World!
```
> NOTE: If the file name is `main.nv` and it has the `main` function. The `navi run` will use it as the program entry.
> You also can execute with `navi run main.nv`.
This code sample demonstrates the basic syntax of Navi.
- The `use` keyword is used to import the `io` module from the standard library.
- The `//` is used to comment a line.
- The `fn` keyword is used to define a function.
- The `main` function is the entry point of the program, the `main` function must have the `throws` keyword, and it can throw an error.
- The `throws` keyword is used to declare a function that can throw an error.
- The `let` keyword is used to declare a variable.
- The `name` variable is a string type, or you can use `let name: string = "World";` to declare it.
- The `message` variable is defined by a string interpolation (Like JavaScript) by using "``", and the `${name}` is a variable reference.
- The `println` function is used to print a string to the console, the `println` and `print` function is default imported from the `std.io` module.
- Use `;` to end a statement.
- Finally, the Code style uses 4 spaces for indentation.\
## Comments
Navi supports 2 types of comments (Like Rust).
The `//` started is a normal comment, and it will be ignored by the compiler.
For example:
```nv,no_run
// This is a normal comment.
fn say(name: string): string {
// This is a normal comment.
// This is the second line of normal comment.
return `Hello ${name}!`;
}
```
There is no multi-line comment in Navi. If you want to write a multi-line comment, just use `//` for each line.
## Doc Comments
A doc comment is started with `///`, and it will be parsed by the compiler and generate documentation. You can write Markdown in it.
For example:
````nv,no_run
/// A struct doc comment.
struct User {
/// The user's name.
name: string,
}
impl User {
/// This is a doc comment for a function.
///
/// ## Args
///
/// - name: The name of the person to say hello to.
///
/// ```nv
/// let user = User { name: "Navi" };
/// assert_eq user.say(), "Hello Navi!";
/// ```
fn say(self): string {
return `Hello ${self.name}!`;
}
}
````
### Doctest
You can write Markdown Code Block in your doc comment, and use `navi test --doc` to run the doc tests.
Like regular tests, doc tests use the `assert`, `assert_eq`, and `assert_ne` keywords for assertion.
For example:
````nv,no_run
/// This is a doc comment for a function.
///
/// ```nv
/// let s = say("World");
/// assert_eq s, "Hello";
/// ```
fn say(name: string): string {
return `Hello ${name}!`;
}
````
Then you can run `navi test --doc` to run the doc test.
```shell
$ navi test --doc
test doc `say` . ok
thread 'main' at 'assertion failed: s == "Hello"', main:9
left: Hello World!
right: Hello
```
This will parse the code block in the doc comment and run it.
### Annotation for doctest
Code blocks can be annotated with attributes that help `navi test` do the right thing when testing your code:
- `ignore`: Ignore doc test (No compile and run).
- `should_panic`: This code should panic or assert failed.
- `no_run`: This code should pass compile but not run.
- `compile_fail`: This code block should fail to compile.
#### For example:
Expect to ignore (No compile and run)
````nv
/// ```nv,ignore
/// fn foo() {
/// ```
````
Expect to **panic** or **assert failed**
````nv
/// ```nv,should_panic
/// assert_eq 1 == 2;
/// ```
````
Expect to **pass compile** but **not run**
````nv
/// ```nv,no_run
/// loop { };
/// ```
````
Expect to **compile failed**
````nv
/// ```nv,compile_fail
/// a = 1
/// ```
````
## Values {#value}
### Primitive Types
| Type | Rust Equivalent | Description | Example |
|----------|-----------------|--------------------------|-------------------------------------------|
| [int] | i64 | A signed integer type | `1`, `-29`, `0` |
| [bool] | bool | A boolean type. | `true`, `false` |
| [float] | f64 | A floating point type | `1.0`, `-29.0`, `0.0` |
| [string] | str | A immutable UTF-8 string | `"Hello, 世界"`
`` `Hello ${1 + 2}` `` |
| [char] | char | A single character | `'a'`, `'b'`, `'c'` |
::: info
💡 Navi only has [int] and [float] types, all `int` are stored as _int64_, and all `float` are stored as _float64_ in internal.
There is no int8, uint8, int16, uint16, int32, uint32, float32, and etc.
:::
### Primitive Values
| Name | Description |
|--------------------|--------------------------------|
| `true` and `false` | [bool] values |
| `nil` | Set an [optional] value to nil |
### Integer {#int}
In Navi, the `int` type is a signed integer type, and it is 64-bit on all platforms. This means it can hold values from `-9223372036854775808` to `9223372036854775807`.
We don't have uint type or other integer types.
```nv
let n = 246;
let n1 = -100;
```
You can use `_` to separate digits in a number (`int`, `float`), it will be **ignored** by the compiler. This is useful for large numbers.
```nv
let amount = 1_000_000;
let price = 123_456_789.123_456;
```
### Float {#float}
Navi has a `float` type (53 bits of precision), and it is 64-bit on all platforms.
```nv
let v = 3.14;
let v1 = -2.0;
let v2 = 0.0;
let v3 = 10.23e+10;
let v4 = 2.0e+2;
```
### Bool {#bool}
Navi has a `bool` type, and it has two values: `true` and `false`.
```nv, no_run
let passed = true;
if (passed) {
println("Passed!");
} else {
println("Failed!");
}
let passed = false;
```
### String {#string}
Use double quotes (`""`) or backticks (` `` `) to create a `string` type.
In Navi all strings are **IMMUTABLE**, you can't change the value of a string.
```nv,no_run
fn main() throws {
let message = "Hello, World 🎉!";
println(message);
println(`chars len: ${message.len()}`);
println(`bytes len: ${message.bytes().len()}`);
}
```
Output:
```shell
$ navi run
Hello, World 🎉!
chars len: 15
bytes len: 18
```
#### Escape Sequences
| Escape Sequences | Description |
|------------------|-----------------|
| `\n` | Newline |
| `\r` | Carriage return |
| `\t` | Tab |
| `\\` | Backslash |
| `\"` | Double quote |
| `\'` | Single quote |
If you use `\` in a string outside of an escape sequence, it will be ignored.
```nv,no_run
fn main() throws {
println("\"Hello, \nWorld!\"");
println("Hello, \\nWorld!");
println("Unknown escape sequence: \a");
}
```
Output:
```shell
$ navi run
"Hello,
World!"
Hello, \nWorld!
Unknown escape sequence: a
```
### String Interpolation
String interpolation is a way to construct a new String value from a mix of constants, variables, literals, and expressions by including their values inside a [string] literal.
Navi's string interpolation is similar to JavaScript's template literals.
Use ` `` ` to create a string with interpolation, use `${}` to insert a value or expression.
```nv
let name = "World";
let hello = `Hello, ${name}!`;
// You can write multi-line string interpolation.
let hello = `
Hello, ${name}!
`;
```
#### ToString interface
The `ToString` interface is a built-in interface, and it has a `to_string` method, you can use it to convert a value to a string.
If you use any type that implements the `ToString` interface in string interpolation, it will call the `to_string` method to convert the value to a string.
For example:
```nv
struct User {
name: string
}
impl User {
pub fn to_string(self): string {
return self.name;
}
}
let user = User { name: "Navi" };
let message = `Hello, ${user}!`;
assert_eq message, "Hello, Navi!";
```
### Char
Navi has a `char` type, and it represents a single Unicode scalar value.
```nv
let c = 'a';
let c1: char = '🎉';
```
### Byte and Bytes
Navi not have byte type, but you can use `int` to represent a byte value, we can use `b''` to create a byte value from a char.
```nv
let b = b'a';
assert_eq b, 97;
```
Use `b""` to create a `Bytes` type from a string.
```nv
let bytes = b"Hello, World!";
// Now `bytes` is a `Bytes` type.
assert_eq bytes.len(), 13;
```
See also: [std.io.Bytes](/stdlib/std.io#std.io.Bytes)
### Assignment
Use the `let` keyword to declare a variable to an identifier, the variable is mutable.
```nv,no_run
// main.nv
let name = "World";
let pi = 3.14;
let passed = true;
fn main() throws {
name = "Navi";
pi = 3.1415926;
passed = false;
let message = `Hello ${name}, pi: ${pi}, passed: ${passed}!`;
println(message);
}
```
Output:
```shell
Hello Navi, pi: 3.1415926, passed: false!
```
You can declare a variable with a type.
```nv
let name: string = "World";
let pi: float = 3.14;
let passed: bool = true;
```
### Type Casting
Use `as` to cast a value to a type, this is **zero-cost** type casting.
You can cast a value from [int] to [float], or from [float] to [int].
| FROM | TO |
|-------|-------|
| int | float |
| float | int |
| bool | int |
```nv
test "cast" {
let n = 100 as float;
assert_eq n, 100.0;
let n = 3.1415 as int;
assert_eq n, 3;
let n = true as int;
assert_eq n, 1;
let n = false as int;
assert_eq n, 0;
}
```
The following are invalid type casting:
```nv,compile_fail
let n = 300 as string; // unable cast type `int` to `string`
let n = 3.1415 as string; // unable cast type `float` to `string`
let n = "10" as int; // unable cast type `string` to `int`
let n = "10" as float; // unable cast type `string` to `float`
let n = 0 as bool; // unable cast type `int` to `bool`
let n = true as float; // unable cast type `bool` to `float`
```
### Type Conversion
Use `parse_int`, and `parse_float` to convert an `string` to a `int` or `float`, the return value is an optional type. If the string value is invalid, it will return `nil`.
Use `to_string` to convert all [Primitive Types] to a `string`, this is always successful.
```nv
test "parse_int" {
let n = "100".parse_int();
assert_eq n, 100;
let n = "abc100".parse_int();
assert_eq n, nil;
let n = "100abc".parse_int();
assert_eq n, nil;
let n = 3.1415 as int;
assert_eq n, 3;
let n = true as int;
assert_eq n, 1;
let n = false as int;
assert_eq n, 0;
}
test "to_float" {
let n = "100".parse_float();
assert_eq n, 100.0;
let n = "3.1415".parse_float();
assert_eq n, 3.1415;
let n = "3.9abc".parse_float();
assert_eq n, nil;
let n = 3 as float;
assert_eq n, 3.0;
}
test "to_string" {
let n = 100.to_string();
assert_eq n, "100";
let n = 3.1415.to_string();
assert_eq n, "3.1415";
let n = true.to_string();
assert_eq n, "true";
let n = false.to_string();
assert_eq n, "false";
}
```
## Testing
You can use the `test` keyword to declare a test function in any Navi file, it will be run when you execute `navi test`.
There are built-in `assert`, `assert_eq`, and `assert_ne` keyword for assertion.
```nv
use std.io;
fn say(name: string): string {
return `Hello ${name}!`;
}
test "say" {
let message = say("World");
assert message == "Hello World!";
assert_eq message, "Hello World!";
assert_ne message, "";
}
```
Output:
```shell
$ navi test
test main.nv . ok
All 1 tests 1 passed finished in 0.03s
```
Like `navi run`, you can use `navi test main.nv` to run a specific file, if you don't pass a file name, it will run all files in the current directory (Like `navi test .`).
The code in the `test` block will be ignored by the compiler when you execute `navi run`.
### Test Declarations
You can use the `test` keyword to declare a test function, followed by a string literal as the test name, and then a block of code.
The `test` block can at anywhere in a Navi file, but it is recommended to put it at the end of the file.
```nv
use std.io;
fn say(name: string): string {
return `Hello ${name}!`;
}
// Here is ok
test "say" {
let message = say("World");
assert message == "Hello World!";
assert_eq message, "Hello World!";
assert_ne message, "";
}
fn add(a: int, b: int): int {
return a + b;
}
test "add" {
let result = add(1, 2);
assert result == 3;
assert_eq result, 3;
assert_ne result, 0;
}
```
Output:
```shell
$ navi test
test main.nv .. ok in 1ms
All 2 tests 2 passed finished in 0.02s.
```
### Test Failures
The test runner will print the error message when a test fails, and with an `exit 1` code to let CI know the test failed.
```nv
test "expect to fail" {
assert true == false;
}
test "expect to fail with message" {
assert_eq 1, 2, "1 != 2";
}
```
Output:
```shell
$ navi test
Testing
test main.nv .. fail in 708ms
main expect to fail
thread 'thread 1#' at 'assertion failed: true == false', main.nv:2
stack backtrace:
0: test#0()
at main.nv:2
main expect to fail with message
thread 'thread 1#' at '1 != 2', main.nv:6
left: 1
right: 2
stack backtrace:
0: test#1()
at main.nv:6
All 2 tests 0 passed, 2 failed finished in 0.79s.
```
### Track Caller
The `#[track_caller]` attribute can be used to mark a function as implicit caller location. When a function is marked with `#[track_caller]`, the panic call stack will not include this function.
This is useful when you want to hide the internal implementation details of a function from the panic call stack.
For example we have a custom assert function:
```nv
#[track_caller]
fn assert_success(value: bool) {
assert value == true;
}
test "assert_success" {
assert_success(true);
assert_success(false);
}
```
When the test faill, the panic call stack will not include the `assert_success` function:
```shell
error: thread 'thread 1#' at 'assertion failed: value == true', test.nv:8
stack backtrace:
0: test#0()
at test.nv:8
```
### Caller Location
We can use `call_location` method in `std.backtrace` module to get the caller, it will return a `CallerLocation` type that contains the file and line number of the caller.
And the `caller_locations` function can returns `[CallerLocation]` that contains a ordered list by call stack.
```nv
use std.backtrace;
fn assert_success(value: bool) {
if (!value) {
let caller = backtrace.caller_location()!;
panic `assertion failed on caller: ${caller.file}:${caller.line}`;
}
}
test "caller_location" {
assert_success(false);
}
```
Output:
```
caller_location
error: thread 'thread 1#' at 'assertion failed on caller:test.nv:4', test.nv:6
stack backtrace:
0: assert_success(bool)
at test.nv:6
1: test#0()
at test.nv:11
```
## Variable
### Declarations
The syntax of variable declarations is:
```
[] :[] =
```
where:
- `declaration_mode` - is the variable mode, we can use `let`, `cost`.
- `let` - declare a mutable variable.
- `const` - declare an immutable variable.
- `type` - used to declare the variable type, such as `int`, `string`, or a optional type `int?`, `string?`.
- `identifier` - variable name.
- `expression` - the value of the variable, can be any expression.
### Identifier
An identifier is a name used to identify a variable, function, struct, or any other user-defined item. An identifier starts with a letter or underscore `_`, followed by any number of letters, underscores, or digits.
It is recommended to use `snake_case` for identifiers, e.g. `my_var`, `my_function_name`.
They must not be a keyword. See [Keywords](#keywords) for a list of reserved keywords.
The following are valid identifiers:
```nv
const name = "World";
let _name = "World";
let name_ = "World";
let _name_ = "World";
let name1 = "World";
```
And they are invalid identifiers:
```nv,compile_fail
let 1name = "World";
let name-1 = "World";
// `use` is a keyword.
let use = "World";
```
### Variable Scope
Variables are scoped to the block in which they are declared. A block is a collection of statements enclosed by `{}`.
```nv,no_run
const name = "Name in global scope";
fn main() throws {
let name = "World";
println(`Hello ${name}!`);
foo();
}
fn foo() {
println(`Hello ${name}!`);
}
```
Output:
```shell
$ navi run
Hello World!
Hello Navi!
Hello World!
Hello Name in global scope!
```
### Const
The `const` keyword is used to declare an immutable variable, and it must have a value. When the value is assigned, it can't be changed.
```nv
const page_size = 200;
```
## Operator
Like other programming languages, Navi has a set of operators for performing arithmetic and logical operations.
| Operator | Relevant Types | Description | Example |
|------------|----------------|----------------------------------------------------------------------------------|--------------|
| `+` | [int], [float] | Addition | `1 + 2` |
| `+=` | [int], [float] | Addition | `a += 1` |
| `-` | [int], [float] | Subtraction | `1 - 2` |
| `-=` | [int], [float] | Subtraction | `a -= 1` |
| `*` | [int], [float] | Multiplication | `1 * 2` |
| `*=` | [int], [float] | Multiplication | `a *= 1` |
| `/` | [int], [float] | Division.
Can cause Division by Zero for integers. | `1 / 2` |
| `/=` | [int], [float] | Division | `a /= 1` |
| `%` | [int], [float] | Modulo | `1 % 2` |
| `%=` | [int], [float] | Modulo | `a %= 1` |
| `-a` | [int], [float] | Negation | `-1` |
| `a?.` | [optional] | Optional | `user?.name` |
| `a \|\| 1` | [optional] | Unwrap [optional] value or use default value.
| `a \|\| 0` |
| `a \|\| b` | [bool] | If `a` is `true`, returns `true` without evaluating `b`. Otherwise, returns `b`. | |
| `a && 1` | [bool] |
| `a!` | [optional] | Unwrap [optional] value or panic | `a!` |
| `a == b` | [int], [float], [bool], [string] ... | `a` equal to `b` | `1 == 2` |
| `a == nil` | [optional] | An [optional] value equal to nil | `a == nil` |
| `a != b` | [int], [float], [bool], [string] ... | `a` not equal to `b` | `1 != 2` |
| `a != nil` | [optional] | An [optional] value not equal to nil | `a != nil` |
```nv
test "test" {
assert 1 + 2 == 3;
assert 1 - 2 == -1;
assert 1 * 2 == 2;
assert 1 / 2 == 0;
assert 1 % 2 == 1;
assert 1 == 1;
assert 1 != 2;
assert 1 < 2;
assert 1 <= 2;
assert 1 > 0;
assert 1 >= 0;
assert -1 == -1;
let a: string? = nil;
assert a?.len() == nil;
let a: string? = "Hello";
assert a?.len() == 5;
}
test "test assignment" {
let a = 1;
a += 1;
assert a == 2;
a -= 1;
assert a == 1;
a *= 2;
assert a == 2;
a /= 2;
assert a == 1;
let a = 10;
a %= 3;
assert a == 1;
}
```
Output:
```shell
$ navi test
Testing .
test main.nv .. ok in 1ms
All 2 tests 2 passed finished in 0.02s.
```
## Array
Array is a collection of items, in Navi array is a mutable collection.
Use `[]` to declare an array, every array must have a type, you can't create an array without a type.
The left side array type is optional, if array init with items, the type will be inferred.
```nv
struct Item {
name: string
}
test "array" {
let a = [1, 2, 3];
let b = ["Rust", "Navi"];
let c: [string] = ["Foo"];
assert a.len() == 3;
assert b.len() == 2;
// get array item
assert a[1] == 2;
assert b[0] == "Rust";
// set array item
a[1] = 3;
assert a[1] == 3;
// Init a struct array
let items: [Item] = [
{ name: "foo" },
{ name: "bar" },
{ name: "baz" }
];
assert_eq items[2].name, "baz";
}
```
If you init a empty array, you must declare the type.
```nv
let items: [string] = [];
let items: [int] = [];
```
### Get & Set Item
Use `[idx]`, `[idx]=` to get and set an item from the array, the index must be an [int] type.
```nv,should_panic
let a = ["Rust", "Navi"];
a[0]; // "Rust"
a[1]; // "Navi"
a[2]; // panic: index out of bounds
a[0] = "Rust 1";
a[0]; // "Rust 1"
```
### Mutate Array
There are `push`, `pop`, `shift`, `unshift` ... methods in Array, you can use them to mutate an array.
```nv
test "push | pop" {
let items: [string] = [];
items.push("foo");
items.push("bar");
assert_eq items.len(), 2;
assert_eq items[0], "foo";
assert_eq items[1], "bar";
assert_eq items.pop(), "bar";
assert_eq items.pop(), "foo";
assert_eq items.pop(), nil;
}
test "shift | unshift" {
let items: [string] = [];
items.unshift("foo");
items.unshift("bar");
assert_eq items.len(), 2;
assert_eq items, ["bar", "foo"];
assert_eq items.shift(), "bar";
assert_eq items.len(), 1;
assert_eq items.shift(), "foo";
assert_eq items.len(), 0;
assert_eq items.shift(), nil;
let items = ["foo", "bar"];
assert_eq items.shift(), "foo";
assert_eq items.len(), 1;
assert_eq items.shift(), "bar";
assert_eq items.len(), 0;
assert_eq items.shift(), nil;
}
```
### Nested Array
The array can be nested.
```nv
let items: [[string]] = [
["foo", "bar"],
["baz", "qux"],
];
let numbers = [[1, 2], [3, 4]];
```
## Map
Map is a collection of key-value pairs, in Navi map is a mutable collection.
Use `{:}` to declare a map, every map must have a type, you can't create a map without a type.
You use use any built-in type as a key, and any type as a value.
```nv
let a: = {"name": "Navi", "version": "0.1.0"};
let b: = {1: 1.0, 2: 2.0};
```
If you init a empty map, you must declare the type, otherwise the type will be inferred.
```nv
let a: = {:};
let a: = {:};
let c = {1: "foo", 2: "bar"};
let d = {"name": "Navi", "version": "0.1.0"};
```
### Get & Set Item
Use `[key]`, `[key]=` to get and set an item from the map, the key must be a type that can be compared.
```nv
let items = {"name": "Navi", "version": "0.1.0"};
assert_eq items["name"], "Navi";
assert_eq items["version"], "0.1.0";
assert_eq items.len(), 2;
assert_eq items.keys(), ["name", "version"];
assert_eq items.values(), ["Navi", "0.1.0"];
items["name"] = "Navi 1";
assert_eq items["name"], "Navi 1";
```
## Struct
The Navi struct is a collection of fields, and it is a [value] type, it's like a struct in Go and Rust.
### Declare a Struct
Use the `struct` keyword to declare a struct, and use `.` to access a field.
- The struct name must be an [identifier] with `CamelCase` style, e.g.: `User`, `UserGroup`, `UserGroupItem`.
- And the field name must be an [identifier], with `snake_case` style, e.g.: `user_name`, `user_group`, `user_group_item`.
- The filed type can be a type or an [optional] type.
- The field can have a default value, e.g.: `confirmed: bool = false`, and then you can create a struct instance without the `confirmed` field.
```nv
struct User {
name: string,
id: int,
profile: Profile?,
// default value is `false`
confirmed: bool = false,
}
struct Profile {
bio: string?,
city: string?,
tags: [string] = [],
}
```
To create a struct instance, use `StructName { field: value }` syntax.
If the variable name is the same as the field name, you can assign it in short syntax, e.g.: `name` is the same as `name: name`.
```nv, ignore
let name = "Jason Lee";
let id = 100;
let user = User { id, name }; // This is same like `User { id: id, name: name }`
```
::: info
In the current version, you must assign `nil` to an [optional] field if you don't want to set a [value].
We will support [optional] field default value to `nil` in the future.
:::
```nv, ignore
test "user" {
let user = User {
name: "Jason Lee",
id: 1,
profile: {
bio: nil,
city: "Chengdu",
},
};
assert_eq user.name, "Jason Lee";
assert_eq user.confirmed, false;
assert_eq user.profile?.bio, nil;
assert_eq user.profile?.city, "Chengdu";
assert_eq user.tags.len(), 0;
}
```
### Implement a Struct
Use `impl` to declare a struct method. The `self` is a keyword, it is a reference to the current struct instance.
Unlike Rust, you don't need to declare `self` as the first parameter.
Use `impl .. for` to implement a interface for a struct. This is a optional way for let us write a clearly code, if the struct have a method can matched the interface, it same as the `impl .. for` implementation.
```nv, ignore
impl User {
fn new(name: string): User {
return User {
name: name,
id: 0,
profile: nil,
};
}
fn say(self): string {
return `Hello ${self.name}!`;
}
}
impl ToString for User {
fn to_string(self): string {
return self.name;
}
}
```
- `new` is a **Static Method**, and it can be called by `User.new`.
- `say` is an **Instance Method**, and it can be called by `user.say()`.
```nv, ignore
fn main() throws {
let user = User.new("Sunli");
println(user.say());
}
```
### Struct Attributes
Use `#[serde(attr = ...)]` to declare a struct serialize and deserialize attributes.
#### `#[serde(rename_all = "...")]`
Rename all the fields (if this is a struct) or variants (if this is an enum) according to the given case convention. The possible values are `"lowercase"`, `"UPPERCASE"`, `"PascalCase"`, `"camelCase"`, `"snake_case"`, `"SCREAMING_SNAKE_CASE"`, `"kebab-case"`, `"SCREAMING-KEBAB-CASE"`.
```nv, ignore
#[serde(rename_all = "camelCase")]
struct User {
user_name: string,
user_group: string,
}
```
Output:
```json
{
"userName": "Sunli",
"userGroup": "Admin"
}
```
#### `#[serde(deny_unknown_fields)]`
Always error during deserialization when encountering unknown fields. When this attribute is not present, by default unknown fields are ignored for self-describing formats like JSON.
::: info NOTE
This attribute is not supported in combination with `flatten`, neither on the outer struct nor on the flattened field.
:::
```nv, ignore
#[serde(deny_unknown_fields)]
struct User {
user_name: string,
user_group: string,
}
```
If we have a source JSON like this:
```json
{
"user_name": "Sunli",
"user_group": "Admin",
"unknown_field": "unknown"
}
```
In this case, we still can deserialize the JSON to a struct, but the `unknown_field` will be ignored.
```nv, ignore
use std.json;
let user = json.parse::(`{ "user_name": "Sunli", "user_group": "Admin", "unknown_field": "unknown" }`);
assert_eq user.user_name, "Sunli";
```
### Field Attributes
#### `#[serde(rename = "...")]`
Serialize and deserialize this field with the given name instead of its Rust name. This is useful for serializing fields as camelCase or serializing fields with names that are reserved Rust keywords.
```nv, ignore
struct User {
#[serde(rename = "name")]
user_name: string,
#[serde(rename = "team")]
user_group: string,
}
```
Output:
```json
{
"name": "Sunli",
"team": "Admin"
}
```
#### `#[serde(alias = "name")]`
Deserialize this field from the given name or from its Navi name. May be repeated to specify multiple possible names for the same field.
```nv, ignore
struct User {
#[serde(alias = "name")]
user_name: string,
#[serde(alias = "team")]
user_group: string,
}
```
So both JSONs are valid:
```json
{
"name": "Sunli",
"team": "Admin"
}
```
```json
{
"user_name": "Sunli",
"user_group": "Admin"
}
```
#### `#[serde(skip)]`
Skip this field: do not serialize or deserialize it. The `skip` field must have a default value, otherwise, it will cause a compile error.
```nv, ignore
struct User {
name: string,
#[serde(skip)]
group: string = "Other",
}
```
Output:
```json
{
"name": "Sunli"
}
```
And also can deserialize the JSON without the `group` field.
```nv, ignore
use std.json;
let user = json.parse::(`{ "name": "Sunli", "group": "Admin" }`);
assert_eq user.name, "Sunli";
// The `group` field is described with `skip`, so it will be ignored, we still get that default value.
assert_eq user.group, "Other";
```
#### `#[serde(flatten)]`
Flatten the contents of this field into the container it is defined in.
This removes one level of structure between the serialized representation and the Rust data structure representation. It can be used for factoring common keys into a shared structure, or for capturing remaining fields into a map with arbitrary string keys.
::: info NOTE
This attribute is not supported in combination with structs that use `deny_unknown_fields`. Neither the outer nor inner flattened struct should use that attribute.
:::
##### Flatten a struct fields
In some cases, we want to flatten a struct field to the parent struct. So we can use `#[serde(flatten)]` to do that.
```nv, ignore
struct User {
name: string,
#[serde(flatten)]
profile: Profile,
}
struct Profile {
bio: string,
city: string,
}
```
Now all fields in the `Profile` struct will be flattened to the `User` struct after serialization.
```json
{
"name": "Sunli",
"bio": "Hello, World!",
"city": "Wuhan"
}
```
##### Capture additional fields
A field of map type can be flattened to hold additional data that is not captured by any other fields of the struct.
```nv, ignore
struct User {
name: string,
#[serde(flatten)]
extra: ,
}
```
For example, we have a lot of unknown fields in the JSON, the all unknown fields will be captured to the `extra` field.
```json
{
"name": "Sunli",
"bio": "Hello, World!",
"city": "Wuhan"
}
```
```nv, ignore
use std.json;
let user = json.parse::(`{ "name": "Sunli", "bio": "Hello, World!", "city": "Wuhan" }`);
assert_eq user.name, "Sunli";
assert_eq user.extra["bio"], "Hello, World!";
assert_eq user.extra["city"], "Wuhan";
```
## Enum
The Navi `enum` is a collection of variants, and it is a [value] type.
### Declare an Enum
Use `enum` keyword to declare an enum, and use `.` to access a variant. Enum only be an [int] type.
```nv
enum UserRole {
Admin,
User,
Guest,
}
```
The first variant will be `0`, the second variant will be `1`, and so on in order.
```nv
enum UserRole {
Admin = 100,
User = 101,
Guest = 103,
}
```
### Convert to a value
Use `as` to convert an enum to a value, this is zero-cost.
```nv, ignore
let a = UserRole.Admin as int;
assert_eq a, 100;
```
### Enum Annotations
Like the struct, `enum` also has annotations for declaring serialize and deserialize attributes.
#### Enum Attributes
##### `#[serde(int)]`
Serialize and deserialize this enum as an integer.
```nv, ignore
#[serde(int)]
enum UserRole {
Admin,
User,
Guest,
}
struct User {
role: UserRole,
}
```
If present, the serialized representation of the enum will be an integer.
```json
{
"role": 100
}
```
Otherwise will use the enum field name as the serialized representation.
```json
{
"role": "Admin"
}
```
##### `#[serde(rename_all = "...")]`
This is the same as the struct `#[serde(rename_all = "...")]`. See [Struct Attributes](#struct-attributes).
Please note that the `rename_all` attribute is not supported in combination with `#[serde(int)]`.
#### Enum Item Attributes
The enum item also has annotations for declaring serialize and deserialize attributes.
##### `#[serde(rename = "...")]`
This is the same as the struct `#[serde(rename = "...")]`. See [Struct Attributes](#struct-attributes).
##### `#[serde(alias = "...")]`
This is the same as the struct `#[serde(alias = "...")]`. See [Struct Attributes](#struct-attributes).
## Interface
The Navi interface is a collection of methods, and it is a [value] type, it's like an interface in Go.
### Declare an Interface
Use the `interface` keyword to declare an interface, and use `.` to access a method.
- The interface name must be an [identifier] with `CamelCase` style, we recommend named interface use a verb, e.g.: `ToString`, `Read`, `Write`.
- And the method name must be an [identifier], with `snake_case` style, e.g.: `to_string`, `read`, `write`.
- We can write a default implementation for a method, and it will be used if the struct does not implement the method.
- The first argument of the method must be `self`, it is a reference to the current struct instance.
```nv, ignore
interface ToString {
pub fn to_string(self): string;
}
interface Read {
fn read(self): string;
fn read_all(self): string {
// This is the default implementation, if the struct does not implement this method, it will be used.
return "";
}
}
fn read_all(reader: Read): ToString {
let s = reader.read();
// Navi's string has a `to_string` method.
return s;
}
```
### Implement an Interface
If any struct has all methods of an interface, it will implement the interface.
```nv, ignore
interface ToString {
fn to_string(self): string;
}
interface Reader {
fn read(self): string;
}
struct User {
name: string
}
impl User {
fn to_string(self): string {
return `${self.name}`;
}
fn read(self): string {
return `Hello ${self.name}!`;
}
}
```
Now we can use a `User` type as a `ToString` or a `Read` interface.
```nv, ignore
fn foo(item: ToString) {
println(item.to_string());
}
fn read_info(item: Reader) {
println(item.read());
}
fn main() throws {
let user = User {
name: "Sunli",
};
foo(user);
read_info(user);
}
```
### Type Assertion
Use `.(type)` to assert an interface to a type.
```nv, compile_fail
interface ToString {
fn to_string(): string;
}
struct User {
}
impl User {
fn to_string(): string {
return "User";
}
}
let a: ToString = "hello";
// Cast a from interface to string.
let b = a.(string);
// now b is a `string`.
let user: ToString = User {};
// Cast user from interface to User.
let user = user.(User);
// now use is `User`.
let user = user.(string); // panic: User can't cast to a string.
```
## Switch
The `switch` statement is used to execute one of many blocks of code.
```nv,no_run
fn get_message(n: int): string {
let message = "";
switch (n) {
case 1:
message = "One";
case 2:
message = "Two";
default:
message = "Other";
}
return message;
}
fn main() throws {
println(get_message(1));
println(get_message(2));
println(get_message(3));
}
```
Output:
```shell
$ navi run
One
Two
Other
```
Use the `switch` keyword to declare a switch statement, the condition must have `()` and return a value. And use `case` and `default` to declare a case.
The `default` case is optional, which means if the condition does not match any case, it will execute the `default` case.
You can also use `{}` to declare a [block] in case of more complex logic.
```nv
fn get_message(n: int): string {
let message = "";
switch (n) {
case 1:
message = "One";
case 2:
message = "Two";
default:
message = "Other";
}
return message;
}
```
### Type switch
The `switch` can also used to assert the dynamic type of an interface variable. Use `let t = val.(type)` to assert the type of `val` in the switch condition.
The `Any` type is a special type that can hold any type of value.
```nv
fn type_name(val: Any): string {
switch (let t = val.(type)) {
case int:
return "int";
case string:
return "string";
case float:
return "float";
case bool:
return "bool";
case :
return "map";
default:
return "unknown";
}
}
test "type_name" {
assert_eq type_name(1), "int";
assert_eq type_name("hello"), "string";
assert_eq type_name(3.14), "float";
assert_eq type_name(true), "bool";
assert_eq type_name({"foo": 1}), "map";
assert_eq type_name(["foo"]), "unknown";
}
```
## While
A while loop is used to repeatedly execute an expression until some condition is no longer true.
Use the `while` keyword to declare a while loop, the condition is an [expression] in `()` that returns a [bool] value.
```nv,no_run
fn main() throws {
let n = 0;
while (n < 5) {
println(`${n}`);
n += 1;
}
}
```
Output:
```shell
$ navi run
0
1
2
3
4
```
Use the `break` keyword to exit a while loop.
```nv,no_run
fn main() throws {
let n = 0;
while (true) {
println(`${n}`);
n += 1;
if (n == 2) {
break;
}
}
}
```
Output:
```shell
$ navi run
0
1
```
Use `continue` to jump back to the beginning of the loop.
```nv,no_run
fn main() throws {
let n = 0;
while (n < 5) {
n += 1;
if (n % 2 == 0) {
continue;
}
println(`${n}`);
}
}
```
Output:
```shell
$ navi run
1
3
5
```
## For
For loops are used to iterate over a range, an array, or a map.
Like `while` loop, you can use `break` and `continue` to control the loop.
### Iter a Range {#range}
The range `start..end` contains all values with `start <= x < end`. It is empty if `start >= end`.
The `for (let i in start..end)` statement is used to iterate over a `std.range.Range`.
```nv,no_run
fn main() throws {
for (let n in 0..5) {
if (n % 2 == 0) {
continue;
}
println(`n: ${n}`);
}
}
```
Output:
```shell
$ navi run
n: 1
n: 3
```
Use the `step` method to create a new range with a step.
```nv
let items: [int] = [];
for (let i in (1..10).step(2)) {
items.push(i);
}
assert_eq items, [1, 3, 5, 7, 9];
```
### Iter an Array
The `for (let item in array)` statement is used to iterate over an [array].
```nv,no_run
fn main() throws {
let items = ["foo", "bar", "baz"];
for (let item in items) {
println(item);
}
}
```
Output:
```shell
$ navi run
foo
bar
baz
```
### Iter a Map
The `for (let k, v in map)` statement is used to iterate over a [map].
```nv
let items = {
"title": "Navi",
"url": "https://navi-lang.org"
};
let result: [string] = [];
for (let k, v in items) {
result.push(`${k}: ${v}`);
}
assert_eq result.join(", "), "title: Navi, url: https://navi-lang.org";
```
Output:
```shell
$ navi run
title: Navi
url: https://navi-lang.org
```
## If
Like most programming languages, Navi has the `if` statement for conditional execution.
```nv,no_run
fn main() throws {
let n = 1;
if (n == 1) {
println("One");
} else if (n == 2) {
println("Two");
} else if (n == 3) {
println("Three");
} else {
println("Other");
}
}
```
### If let
The `if let` statement is used to match an [optional] value.
```nv,no_run
fn get_a(a: string?) {
if (let a = a) {
println(a);
} else {
println("a is nil");
}
}
fn main() throws {
get_a("foo");
get_a(nil);
}
```
Output:
```shell
$ navi run
foo
a is nil
```
## Function
Use `fn` keyword to declare a function, the function name must be an [identifier], and the function body must be a [block].
::: info
Navi recommends using `snake_case` for the function name, e.g.: `send_message`, `get_user`, `get_user_by_id`.
And the argument name also uses `snake_case`, e.g.: `title`, `user_id`.
:::
You can define a function at the module level, or in a struct `impl` block.
- The function name must be an [identifier].
- The arguments can be [normal arguments], [keyword arguments] or [arbitrary arguments].
```nv,no_run
fn add(a: int, b: int, args: ..string, mode: string = "+"): string {
let result = a + b;
return `${a} + ${b} = ${result}`;
}
struct User {
name: string,
}
impl User {
fn say(self): string {
return `Hello ${self.name}!`;
}
}
fn main() throws {
println(add(1, 2));
let user = User { name: "Navi" };
println(user.say());
}
```
Output:
```shell
$ navi run
a + b = 3
Hello Navi!
```
### Positional Arguments
Positional arguments are arguments that are passed by position.
Use `name: type` to declare a positional argument, you can put a positional argument after the keyword argument.
```nv
fn add(a: int, b: int, mode: string = "+"): string {
let result = a + b;
return `${a} + ${b} = ${result}`;
}
```
To define an [optional] type for an argument, we use `?` after the type, e.g.: `b: int?`.
```nv,no_run
fn add(a: int, b: int?): string {
// unwrap b or default to 0
let b = b || 0;
let result = a + b;
return `${a} + ${b} = ${result}`;
}
fn main() throws {
println(add(1, 2));
println(add(1, nil));
}
```
Output:
```shell
$ navi run
1 + 2 = 3
1 + 0 = 1
```
### Arbitrary Arguments
Use `..type` to declare an arbitrary argument, the arbitrary argument must be the **last** argument (Except Keyword Arguments).
This is means you can pass any number of arguments to the function. And this argument will be as an array in the function body.
```nv
fn add(one: int, others: ..int): int {
// The `others` is `[int]` type.
let result = one;
for (let n in others) {
result += n;
}
return result;
}
assert_eq add(1, 2, 3, 4, 5), 15;
let others = [2, 3, 4, 5];
assert_eq add(1, ..others), 15;
```
### Keyword Arguments
Keyword arguments (Kw Args) are arguments that are passed by name. They are useful when a function has many arguments or default arguments.
Use `name: value = default` to declare a keyword argument, the keyword argument must be after positional arguments.
```nv,no_run
fn add(a: int, b: int, mode: string = "+", debug: bool = false): string {
if (debug) {
return `a: ${a}, b: ${b}, mode: ${mode}`;
}
let result: int = 0;
if (mode == "-") {
result = a + b;
} else {
result = a - b;
}
return `${a} ${mode} ${b} = ${result}`;
}
fn main() throws {
println(add(1, 2));
println(add(1, 2, mode: "+"));
println(add(1, 2, mode: "-"));
println(add(1, 2, mode: "-", debug: true));
println(add(1, 2, debug: true, mode: "+"));
println(add(1, 2, debug: true));
}
```
Output:
```shell
$ navi run
1 + 2 = -1
1 + 2 = -1
1 - 2 = 3
a: 1, b: 2, mode: -
a: 1, b: 2, mode: +
a: 1, b: 2, mode: +
```
### Function to a Variable
In Navi, the Function is the first-class citizen, it can be assigned to a variable, and it can be passed as an argument to another function.
```nv,no_run
use std.io;
fn add(a: int, b: int): string {
return `${a + b}`;
}
struct User {
name: string,
}
impl User {
fn say(self): string {
return `Hello ${self.name}!`;
}
}
fn main() throws {
let add_fn = add;
println(add_fn(1, 2));
let user = User { name: "Navi" };
let say_fn = user.say;
println(say_fn());
}
```
## Closure
A closure is a function that captures the environment in which it was created. It can capture variables from the surrounding scope.
- The closure type just use `|(type, type): return_type|`.
- If there is no parameter, use `|(): return_type|`.
- If no return type, use `|(type, type)|`.
- If no parameter and return type, use `|()|`.
```nv
fn call_add(f: |(int, int): int|): int {
return f(2, 3);
}
let add: |(int, int): int| = |a, b| {
return a + b;
};
assert_eq add(1, 2), 3;
assert_eq call_add(add), 5;
fn call_add1(f: |(): int|): int {
return f() + 2;
}
assert_eq call_add1(|| {
return 3;
}), 5;
fn call_add2(f: |()|): int {
f();
return 2;
}
assert_eq call_add2(|| {
// do something without return
}), 2;
```
## Optional
Navi provides a modern optional type, which is similar to Rust's `Option` type, to give us a safe way to handle `nil` value, we can avoid the `null pointer exception` in runtime.
Use `type?` to declare an optional type, e.g.: `string?`, `int?`, `float?`, `bool?`, `User?` ...
```nv
// a normal string
let name: string = "Navi";
// an optional string
let optional_name: string? = "Navi";
let optional_name: string? = nil;
```
Now the `optional_name` is an [optional] type, it can be a [string] or `nil`.
### Unwrap Optional
The `!` operator is used to unwrap an [optional] value, if the value is `nil`, it will panic.
It is useful when you want to get a [value] from an [optional] value and you are sure it is not `nil`.
::: warning NOTE
To keep your code safe, when you use `!`, you must be sure it is not `nil`.
If not, don't use it, the [value || default](#unwrap-or-default) is a better way to get a [value] from an [optional] value.
:::
```nv,no_run
fn main() throws {
let name: string? = "Navi";
// This is ok.
println(name!);
let name: string? = nil;
// This will cause a panic.
println(name!);
}
```
### Unwrap or Default
The `||` operator is used to unwrap an [optional] value, if the value is `nil`, it will return the default value.
The right side of `||` can be a [value] or an [expression] that returns a [value], if the left side is not nill, the right side will not be evaluated.
```nv
use std.io;
test "unwrap or default" {
let name: string? = "Navi";
let result = name || "";
// result is a string type
assert_eq result, "Navi";
let name: string? = nil;
let result = name || "";
// result is a string type
assert_eq result, "";
}
```
### More methods
We also provide some methods to handle the [optional] value, such as `map`, `and`, `and_then`, `is_nil`, `map_or`, `unwrap_or`, `expect`, `unwrap_or_else`.
See also: [Optional Methods](/stdlib/lang.optional).
```nv,no_run
// a normal string
let name: string = "Navi";
// an optional string
let optional_name: string? = "Navi";
let optional_name: string? = nil;
fn main() throws {
let name: string? = "Navi";
// This is ok.
println(name!);
// This is also ok.
println(name.unwrap());
println(name.expect("Name is nil"));
println(name.map(|name| {
return `Name length: ${name.len()}`;
}));
println(name.and("And other name"));
println(name.and_then(|name| {
return `And then name: ${name}`;
}));
let name: string? = nil;
println(`name is nil: ${name.is_nil()}`);
println(name.map_or("Default value", |name| name.len()));
println(name.unwrap_or("unwrap_or a default value"));
println(name.or("Or a default value"));
println(name.or_else(|| "Or else a default value"));
// This will cause a panic.
println(name!);
}
test "unwrap or default" {
let name: string? = "Navi";
let result = name || "";
// result is a string type
assert_eq result, "Navi";
let name: string? = nil;
let result = name || "";
// result is a string type
assert_eq result, "";
}
```
## Error
The `throws` keyword on a function to describe that the function can be thrown an error.
::: warning NOTE
All functions whose signature is `throws` must use `try`, `try?` or `try!` keyword before it when you call it.
:::
| Keyword | Description |
|-----------|--------------------------------------------------------------|
| `throws` | The function can throw an error. |
| `try` | The error will be thrown, if the function throws an error. |
| `try?` | If error is thrown, the `try?` will return `nil`. |
| `try!` | If error is thrown, the `try!` will panic. |
| `throw` | Throw an error. |
| `do` | The `do` block is used to handle an error. |
| `catch` | The `catch` block is used to match an error interface. |
| `finally` | The `finally` block is optional, it will always be executed. |
| `panic` | The `panic` function is used to panic the program. |
### Error Interface
By default, `throw` can throw with a [string] or a custom error type that implements the `Error` interface.
::: info TIP
Because Navi has implemented the `Error` interface for [string], you can throw a [string] directly.
:::
```nv
pub interface Error {
fn error(self): string;
}
```
So you can just throw [string]:
```nv, ignore
throw "error message";
```
Or implement the `Error` interface for a custom error type:
```nv
struct MyError {
message: string
}
impl Error for MyError {
// Implement the `error` method for `MyError` struct, then `MyError` can be used as an `Error` interface.
pub fn error(self): string {
return self.message;
}
}
```
We can use `throws` to declare an error type or keep it empty to use default error.
```nv, ignore
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
fn hello_with_custom_error(name: string): string throws MyError {
if (name == "Navi") {
throw MyError { message: "name can't be Navi" };
}
return `Hello ${name}!`;
}
```
For example:
```nv,no_run
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
fn main() throws {
let result = try? hello("Navi");
println(`${result || ""}`);
}
```
### Catch Error
Use `do ... catch` statement to catch an error.
- In the `do` block, you must use `try` keyword before all functions that can throw an error.
- The `catch` block is used to match an error interface, it can have multiple `catch` blocks to match different error types.
- And the `finally` block is optional, it will always be executed.
Every type that implements the `error` method can be used as an `Error` interface.
```nv,ignore
use std.io;
struct MyError {
message: string
}
impl Error for MyError {
// Implement the `error` method for `MyError` struct, then `MyError` can be used as an error interface.
pub fn error(): string {
return self.message;
}
}
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
do {
let result = try hello("Navi");
println(result);
let result1 = try hello("Sunli");
} catch (e) {
println(e.error());
} catch (e: MyError) {
// ...
} finally {
// This block always be executed.
}
```
### Handle Error
#### try
If the function throws an error, the `try` will throw the error.
```nv, no_run
fn hello(name: string): string throws {
if (name == "Navi") {
throw "name can't be Navi";
}
return `Hello ${name}!`;
}
fn main() throws {
let result = try hello("Navi");
// if error is thrown, the `try` will throw the error.
}
```
#### try?
If the function throws an error, the `try?` will return `nil`.
```nv, ignore
let result = try? hello("Navi");
assert_eq result, nil;
let result = try? hello("Sunli");
assert_eq result, "Hello Sunli!";
```
#### try!
If the function throws an error, the `try!` will panic.
```nv, ignore
let result = try! hello("Navi");
// This will cause a panic
```
### Panic
The `panic` keyword is used to panic the program.
```nv, ignore
fn hello(name: string): string {
if (name == "Navi") {
panic "name can't be Navi";
}
return `Hello ${name}!`;
}
```
When `panic` is called, the program will stop running and print the error message.
## Use
The `use` keyword is used to import a module from the standard library or a file.
```nv
use std.io;
use std.url.Url;
fn main() throws {
let url = try Url.parse("https://navi-lang.org");
assert_eq url.host(), "navi-lang.org";
}
```
When you import, the last part of the module name is the name of the module, e.g.: `use std.io` to `io`, `std.url.Url` to `Url`, `std.net.http` to `http`.
### Alias
Sometimes we may want to use a different name for a module, we can use `as` to import a module with an alias.
```nv
use std.url.Url as BaseURL;
let url = try! BaseURL.parse("https://navi-lang.org");
assert_eq url.host(), "navi-lang.org";
```
### Use multiple modules
We can use multiple modules by one `use`.
```nv
use std.{io, url.Url};
fn main() throws {
let url = try Url.parse("https://navi-lang.org");
assert_eq url.host(), "navi-lang.org";
}
```
### Import a Module from local
In Navi, a folder in the current directory is a module, and the module name is the folder name.
For example, we have a struct:
```shell
$ tree
main.nv
models
|── profile
| |── a.nv
| └── b.nv
└── user.nv
utils
|── string.nv
└── url.nv
```
Now you can import them in `main.nv`:
```nv,ignore
use models;
use models.profile;
use utils;
```
## Module System
In Navi a folder in the current directory is a module, and the module name is the folder name.
- The root directory is the `main` module, and uses `main.nv` as the entry file by default.
- The any sub-directory as a sub-module, and `use` the directory name as the module name.
- The root directory can have multiple entry files, and you can use `navi run filename.nv` to run it directly.
- The `pub` keyword is used to export a `struct`, `struct field`, `interface`, `function`, `type`, `enum`, `const`, `let`, then the other modules can use it.
For example, we have a project like this:
```shell
$ tree
main.nv
utils.nv
models
|── user_profile.nv
|── user_profile
| |── profile_a.nv
| └── profile_b.nv
config
|── config_a.nv
└── config_b.nv
```
In this case:
- `main.nv`, `utils.nv` files are in the `main` module, they can access and share members with each other.
- `models` directory is a module named `models`.
- `models/user_profile.nv` will be compiled into `models` module.
- `models/user_profile` directory is a module named `models.user_profile`.
- `modles/user_profile/*.nv` files will be compiled into `models.user_profile` module, they are same like one file.
- `config` directory is a module named `config`.
- `config/*.nv` files will be compiled to the `config` module, they are the same as one file.
::: warning NOTE
If your project has multiple sub-modules, you need to link them by `use` keyword to let the Navi compiler know the module dependency.
Only the used modules will be compiled, this means `navi test` or other commands will not find the sub-directory modules if you don't use them.
:::
For example, in `main.nv`:
```nv, ignore
use models;
use config;
fn main() throws {
}
```
## Type
We have `type` and `type alias` in Navi to create a type based on an existing type.
- `type` is used to create a new type, user can not see the original type, and the new type not have any method of the original type.
And we can use `as` the convert to the original type with zero cost.
- `type alias` is used to create a new name for an existing type, the new name is acutally the same as the original type.
Use `type` keyword to create a newtype based on an existing type.
```nv
type alias Key = string;
type alias Value = int;
type alias MyInfo = ;
let info: MyInfo = {
"foo": 1,
"bar": 2,
};
assert_eq info["foo"], 1;
assert_eq info["bar"], 2;
```
Use `type alias` to create a new name for an existing type.
```nv
type alias MyString = string;
let name: MyString = "Navi";
```
### Type Implementation
You can use `impl` to implement some method to a type alias.
::: warning NOTE
The type alias is not a new type, it is just an alias of the original type, so when you implement that type, the original type will also be changed.
:::
```nv
struct User {
name: string,
}
type NewUser = User;
impl NewUser {
fn new_method(self) {
}
}
```
After this implementation, the `User` type will also have the `new_method` method.
## Union Type
The union type allows us to combine two or more types into one type.
```nv
fn to_string(val: int | string | float): string {
switch (let val = val.(type)) {
case int:
return `int: ${val}`;
case float:
return `float: ${val}`;
case string:
return `string: ${val}`;
}
}
assert_eq to_string(1), "int: 1";
assert_eq to_string(3.14), "float: 3.14";
assert_eq to_string("hello"), "string: hello";
```
It also can be used as a struct field type.
```nv
struct User {
stuff_number: int | string,
}
let user = User {
stuff_number: 1,
};
let user = User {
stuff_number: "one",
};
```
Or with return type.
```nv
fn get_stuff_number(): (int | string) {
return 1;
}
```
## Defer
The `defer` keyword is used to execute a block of code when the current function returns.
This is most like Go's `defer` keyword. It is useful when you want to do some cleanup work, e.g.: close a file, close a database connection, etc.
```nv, no_run
use std.io;
fn main() throws {
defer {
println("defer 1");
}
defer {
println("defer 2");
}
println("Hello");
}
```
Output:
```shell
$ navi run
Hello
defer 2
defer 1
```
## Spawn
Navi has a `spawn` keyword for spawn a coroutine, it is similar to Go's `go` keyword.
```nv
use std.time;
fn main() throws {
let ch: channel = channel();
spawn {
println("This will print 1");
time.sleep(0.1.seconds());
println("This is print from spawn 1");
// Signal that we're done
try! ch.send(1);
}
spawn {
println("This will print 2");
time.sleep(0.1.seconds());
println("This is print from spawn 2");
// Signal that we're done
try! ch.send(1);
}
println("This is printed 3");
// Wait for the spawned task to finish
try ch.recv();
println("All done");
}
```
Unlike Go, Navi is a single-thread language, so the `spawn` is to make code run concurrently, not parallelly.

> Graph from [Concurrency is NOT Parallelism]
See also:
- [Concurrency is NOT Parallelism]
## Channel
The `channel` is a communication mechanism that allows one goroutine to send values to another goroutine.
Use `channel` to create a channel, and use `send` to send a value to the channel, and use `recv` to receive a value from the channel.
```nv,no_run
let ch = channel::();
spawn {
let i = 1;
while (i <= 10) {
try! ch.send(i);
i += 1;
}
}
let i = 1;
while (i <= 10) {
let value = try! ch.recv();
assert value == i;
i += 1;
}
```
## Keywords
The following are reserved keywords in Navi, they can't be used as [identifier].
| Keyword | Description |
|-----------------------------|--------------------------------------------------------------------------------------------|
| `as` | Convert a value to a type. |
| `assert_eq` | assert equal |
| `assert_ne` | assert not equal |
| `assert` | assert |
| `bench` | Benchmark function |
| `benches` | Benchmark group |
| `break` | `break` is used to exit a loop before iteration completes naturally. |
| `case` | `case` for the `switch` statement. |
| `catch` | Use `catch` to catch an error. |
| `const` | Declare a constant. |
| `continue` | `continue` can be used in a loop to jump back to the beginning of the loop. |
| `default` | `default` case for `switch` statement. |
| `defer` | Execute a block of code when the current function returns. |
| `do` | Use `do` to handle an error. |
| `else` | `else` can be used to provide an alternate branch for [if], [switch], [while] expressions. |
| `enum` | Define an enum. |
| `false` | false |
| `finally` | Use `finally` to execute a block of code after `try` and `catch` blocks. |
| `fn` | Declare a function. |
| `for` | [for] loop |
| `if` | [if] statement |
| `impl` | Declare a struct implementation. |
| `in` | key use in [for] loop |
| `interface` | Define a [interface] |
| `let` | Declare a variable. |
| `loop` | an infinite [loop] |
| `nil` | An [optional] value of nil. |
| `panic` | Panic an error. |
| `pub` | Mark a function, struct, interface or enum as public. |
| `return` | Return a value from a function. |
| `select` | Use to select a [channel]. |
| `self` | A reference to the current struct instance. |
| `spawn` | [Spawn] a coroutine. |
| `struct` | Define a struct. |
| `switch` | [switch] statement |
| `test` | Test function |
| `tests` | Test group |
| `throw` | Throw an error. |
| `throws` | Declare a function can throw an [error]. |
| `true` | true |
| `try`
`try?`
`try!` | Use `try` to handle an error. |
| `type` | Create a type alias. |
| `use` | [use] a module from the standard library or a file. |
| `while` | [while] loop |
[Primitive Types]: #primitive-types
[Use]: #use
[Intergers]: #int
[int]: #int
[string]: #string
[Floats]: #float
[float]: #float
[char]: #chars
[optional]: #optional
[bool]: #bool
[String interpolation]: #string-interpolation
[identifier]: #identifier
[identifiers]: #identifier
[values]: #value
[value]: #value
[block]: #block
[expression]: #expression
[Positional Arguments arguments]: #positional-arguments
[Keyword Arguments]: #keyword-arguments
[Kw Argument]: #keyword-arguments
[Arbitrary Arguments]: #arbitrary-arguments
[while]: #while
[loop]: #loop
[for]: #for
[switch]: #switch
[if]: #if
[channel]: #channel
[spawn]: #spawn
[unwrap || default]: #unwrap-or-default
[Unwrap or Default]: #unwrap-or-default
[Concurrency is NOT Parallelism]: https://ics.uci.edu/~rickl/courses/ics-h197/2014-fq-h197/talk-Wu-Concurrency-is-NOT-parallelism.pdf
[Error]: #error
[Type Alias]: #type-alias
[Defer]: #defer
---
# Append content to a file
The [fs.open](/stdlib/std.fs#open) or [File.open](/stdlib/std.fs#File.open) function is used to open a file and return a [std.fs.File](/stdlib/std.fs#std.fs.File) instance, with `flag` argument you can specify the file open mode.
- The `fs.APPEND` flag is used to open the file in append mode.
- The `fs.CREATE` flag is used to create a new file if the file does not exist.
Then use `write_string` method to write the string content to the file.
## Navi Code
```nv, no_run
use std.fs.{self, File};
fn main() throws {
let f = try File.open("output.txt", flag: fs.APPEND | fs.CREATE);
defer {
try! f.close();
}
try f.write_string("Hello, world!\n");
try f.write_string("This is next line!\n");
}
```
The above code will append the content to the file `output.txt`. If the file does not exist, it will create a new file.
After running the above code, the content of the file `output.txt` will be:
```txt
Hello, World!
This is next line!
```
---
---
order: 3
---
# Array
Array for storing multiple values in a single variable.
## Syntax
To initialize an array, use the `[]` operator (It more like Go).
```nvs
let items = [number] { 1, 2, 3 };
items.sum();
// 6
items.len();
// 3
```
## Init array
And you can also to define multiple types of arrays.
```nvs
// a number array
let number_items = [number] { 1, 2, 3 };
// a string array
let string_items = [string] { "hello", "world" };
// a struct array
struct Item {
name: string
}
let struct_items = [Item] {
Item { a: 1, b: "hello" },
Item { a: 2, b: "world" },
};
// a color array
let color_items = [color] { #ff0000, #00ff00, #purple };
```
## Methods
### len
Get the length of the array.
```nvs
let items = [number] { 1, 2, 3 };
items.len();
// 3
```
### iter
Iterate the array.
```nvs
let items = [number]{ 1, 2, 3 };
items.iter((item) => {
item.to_string();
});
```
### slice
Get a slice (Same like JavaScript) of the array.
```nvs
let items = [number] { 1, 2, 3, 4, 5 };
items.slice(1, 3);
// [2, 3]
```
### clear
Remove all items from the array.
```nvs
let items = [number] { 1, 2, 3 };
items.clear();
// 0
```
### reverse
Reverse the array.
```nvs
let items = [number] { 1, 2, 3 };
items.reverse();
// [3, 2, 1]
let str_items = [string] { "a", "b", "c" };
str_items.reverse();
// ["c", "b", "a"]
```
### push
Push an item to the end of the array.
```nvs
let items = [number]{1, 2, 3};
items.push(4);
items;
// [1, 2, 3, 4]
```
### pop
Pop an item from the end of the array.
```nvs
let items = [number]{ 1, 2, 3 };
items.pop();
// 3
items;
// [1, 2]
```
### shift
Insert an item to the beginning of the array.
```nvs
let items = [number] { 1, 2, 3 };
items.shift(0);
items;
// [0, 1, 2, 3]
```
### unshift
Remove an item from the beginning of the array.
```nvs
let items = [number]{ 1, 2, 3 };
items.unshift();
// 1
items;
// [2, 3]
```
### remove
Remove an item from the array, and return the removed item.
```nvs
let items = [string] { "a", "b", "c" };
items.remove(1);
// "a"
items;
// ["b", "c"]
```
### get
Get an item at the given index.
```nvs
let items = [string] { "a", "b", "c" };
items.get(1);
// "b"
```
### set
Set an item in the array.
```nvs
let items = [string] { "a", "b", "c" };
items.set(1, "d");
items;
// ["a", "d", "c"]
```
### iter
Create a [Iterator] for the array.
```nvs
let items = [number] { 1, 2, 3 };
let iter = items.iter();
iter.next();
// 1
```
### clone
Returns a copy of the array.
```nvs
let items = [number]{ 1, 2, 3 };
let items1 = items.clone();
// items1 is [1, 2, 3]
items.clear()
// items is [], items1 still is [1, 2, 3]
```
## Methods for `[number]`
### sum
Get the sum of the array.
```nvs
let items = [number] { 1, 2, 3 };
items.sum();
// 6
```
[iterator]: ./iterator
### avg
Get the average of the array.
```nvs
let items = [number] { 1, 2, 3 };
items.avg();
// 2
```
### min
Get the minimum value of the array.
```nvs
let items = [number] { 1, 2, 3 };
items.min();
// 1
```
### max
Get the maximum value of the array.
```nvs
let items = [number] { 1, 2, 3 };
items.max();
// 3
```
### avg
Get the average of the array.
```nvs
let items = [number] { 23, 5, 28 };
items.avg();
// 18.666666666666668
```
### sort
Sort the array.
```nvs
let items = [number] { 2, 4, 3, 1 };
items.sort();
// items is [1, 2, 3, 4]
```
## Methods for `[string]`
### join
Join the array with the given separator.
```nvs
let items = [string] { "hello", "world" };
let a1 = items.join(" ");
// a1 is "hello world"
let items1 = "hello world".split(" ").join(",");
// items1 is "hello,world"
```
---
# Assign
Like most programming languages, use `=` to assign a value to a variable.
```nvs
let a = 10 + 20;
// a = 30
a = a + 1;
// a = 31
```
We can also use `+=`, `-=`, `*=`, `/=` to simplify the assignment operation.
```nvs
let a = 10;
a += 1;
// a = 11
a -= 1;
// a = 10
a *= 2;
// a = 20
a /= 2;
// a = 10
```
When you first declare a variable, you cannot use `let` to declare the same variable again, the compiler will report an error.
```nvs
let a = 10;
let a = 20;
// Compile error
```
---
---
order: 4
---
# barstate
| Function Name | Description |
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| is_history:`bool` | Returns `true` if current bar is a historical bar, `false` otherwise. |
| is_new:`bool` | Returns `true` if script is currently calculating on new bar, `false` otherwise. |
| is_confirmed:`bool` | Returns `true` if the script is calculating the last (closing) update of the current bar. The next script calculation will be on the new bar data. |
| is_realtime:`bool` | Returns `true` if current bar is a real-time bar, `false` otherwise. |
| is_last:`bool` | Returns `true` if current bar is the last bar in barset, `false` otherwise. This condition is true for all real-time bars in barset. |
---
---
order: 0
---
# Bool
Bool is a type of object that represents a boolean value.
## Create a bool
```nvs
let a = true;
let b = false;
```
## Methods
### to_string
Convert a bool to a string.
```nvs
let a = true;
a.to_string();
// "true"
```
### to_number
Convert a bool to a number.
```nvs
let a = true;
a.to_number();
// 1
a = false;
a.to_number();
// 0
```
---
# Break
`break` use for exit current loop.
The following example shows how to use `break` to find the first K line where `close` is greater than `open`:
> `quote` is from stdlib.
```nvs
let i = 0;
while (true) {
if (quote.close[i] > quote.open[i]) {
break;
}
i = i + 1;
}
```
---
# Break
`break` 用于退出当前循环。
下面的例子展示了如何使用 `break` 来找到第一个 `close` 大于 `open` 的 K 线,当满足条件时,退出循环。
> `quote` is from stdlib.
```nvs
let i = 0;
while (true) {
if (quote.close[i] > quote.open[i]) {
break;
}
i = i + 1;
}
```
---
# Check if a file exists
The [fs.exists](/stdlib/std.fs#exists) function is used to check if a file exists, the first argument is a string of the file path.
## Navi Code
```nv,no_run
use std.fs;
fn main() {
if (fs.exists("path/to/file.txt")) {
println("File exists");
} else {
println("File does not exist");
}
}
```
---
---
order: 9
---
# ClosedTrade
ClosedTrade is a type of object that represents a closed trade.
## Methods
### size
`size(): number`
Returns the size of the closed trade.
Returns the direction and the number of contracts traded in the closed trade. If the value is > 0, the market position was long. If the value is < 0, the market position was short.
### entry_price
`entry_price(): number`
Returns the entry price of the closed trade.
### entry_time
`entry_time(): datetime`
Returns the entry time of the closed trade.
### exit_price
`exit_price(): number`
Returns the exit price of the closed trade.
### exit_time
`exit_time(): datetime`
Returns the exit time of the closed trade.
### profit
`profit(): number`
Returns the profit/loss of the closed trade. Losses are expressed as negative values.
### profit_percent
`profit_percent(): number`
Returns the profit/loss of the closed trade, expressed as a percentage of the initial capital. Losses are expressed as negative values.
### max_runup
`max_runup(): number`
Returns the maximum run-up of the closed trade, i.e., the maximum possible profit during the trade.
### max_runup_percent
`max_runup_percent(): number`
Returns the maximum run-up of the closed trade, i.e., the maximum possible profit during the trade, expressed as a percentage and calculated by formula: `Highest Value During Trade / (Entry Price x Quantity)`.
### max_drawdown
`max_drawdown(): number`
Returns the maximum drawdown of the closed trade, i.e., the maximum possible loss during the trade.
### max_drawdown_percent
`max_drawdown_percent(): number`
Returns the maximum drawdown of the closed trade, i.e., the maximum possible loss during the trade, expressed as a percentage and calculated by formula: `Lowest Value During Trade / (Entry Price x Quantity)`.
### commission
`commission(): number`
Returns the commission paid for the closed trade.
---
---
order: 7
---
# Color
Color is a type of object that represents a color.
## Create a color
```nvs
let color = #ff0000;
let color1 = #red;
```
---
---
order: 2
---
# Comment
Same as in many common languages, comments are denoted by `//`.
```nvs
// Here is a line comment
let a = 1; // Here is a comment after a statement
```
---
# Continue
`continue` statement is used to skip the remaining statements in the loop body.
The following example counts the number of up periods in the last 10 periods.
```nvs
let n = 0;
for (let i = 0 to 10) {
if (quote.close[i] <= quote.open[i]) {
continue;
}
n = n + 1;
}
```
---
# Continue
`continue` 语句用于跳过循环体中的剩余语句。
下面的例子统计了最近 10 个周期中的上涨周期数。当遇到 `quote.close[i] <= quote.open[i]` 时,跳过不进行相加,继续下一个周期。
```nvs
let n = 0;
for (let i = 0 to 10) {
if (quote.close[i] <= quote.open[i]) {
continue;
}
n = n + 1;
}
```
---
# Copy a file
The [fs.copy](/stdlib/std.fs#copy) function is used to copy a file from one location to another, the first argument is a string of the source file path, and the second argument is a string of the destination file path.
If the destination file exists, it will **overwrite** the file.
::: info
The `fs.copy` function is only used to copy a **file** or a **symlink**.
:::
```nv, no_run
use std.fs;
fn main() throws {
try fs.copy("path/to/source.txt", "path/to/destination.txt");
}
```
## Link a file
We have [fs.link](/stdlib/std.fs#link) method to create a hard link to a file, and [fs.symlink](/stdlib/std.fs#symlink) method to create a symbolic link to a file.
```nv, no_run
use std.fs;
fn main() throws {
try fs.link("path/to/source.txt", "path/to/destination.txt");
try fs.symlink("path/to/source.txt", "path/to/destination.txt");
}
```
You also can use [fs.readlink](/stdlib/std.fs#readlink) method to read the target of a symbolic link, this will return the string path of the link source.
```nv, no_run
use std.fs;
fn main() throws {
try fs.symlink("path/to/source.txt", "path/to/destination.txt");
let target = try fs.readlink("path/to/destination.txt");
println(target);
// Output: path/to/source.txt
}
```
And the [fs.unlink](/stdlib/std.fs#unlink) method to remove a link.
> Actually, the `fs.unlink` is a alias of `fs.remove_file`.
---
---
order: 2
---
# DateTime
We can use `time` package to create a `DateTime` object.
Time for create a time object.
## Usage
```nvs
let t = time.parse("2023-04-13 09:45:26")
// t is a DateTime object
t.year
// 2023
t.month
// 4
t.day
// 13
let t1 = time.parse("invalid time")
// t1 is nil
```
## Class Methods
### new
Create a new DateTime object.
```nvs
let t = time.new(2023, 1, 11, 0, 0, 0);
// t is a DateTime object
export let t_str = t.to_string();
// t_str is "2023-01-11T00:00:00Z"
```
### parse (time)
Parse a string to a DateTime object, default time format is [RFC3339].
If the time format is invalid, it will return `nil`.
If there not timezone, use `UTC` as default.
```nvs
let t = time.parse("2023-04-13T14:08:33-11:00");
```
Or it compibility to supports `%Y-%m-%d %H:%M:%S` format.
```nvs
let t = time.parse("2023-04-13 09:45:26");
```
### parse (time, format)
You can use `parse` to pass 2 arguments, the first is the time string, the second is the time format.
```nvs
time.parse("2023-04-13 09:45:26", "%Y-%m-%d %H:%M:%S");
time.parse("2023-04-13 09:45:26", "%Y-%m-%d %H:%M:%S %z");
time.parse("04/13/2023 09:45:26", "%m/%d/%Y %H:%M:%S");
```
More details of the time format, see [Time Format].
## Instance Methods
### year
Return the year of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26")
t.year
// 2023
```
### month
Return the month of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.month;
// 4
```
### day
Return the day of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.day;
// 13
```
### hour
Return the hour of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.hour;
// 9
```
### minute
Return the minute of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.minute;
// 45
```
### second
Return the second of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.second;
// 26
```
### timestamp
Return the [Unix Timestamp] (in second) of the time.
```nvs
let t = time.parse("2023-04-13 09:45:26 +08:00");
t.timestamp;
// 1681350326
```
### format
Return the time string with the [Time Format] format.
```nvs
let t = time.parse("2023-04-13 09:45:26 +08:00");
t.format("%Y-%m-%d %H:%M:%S %z");
// 2023-04-13 09:45:26 +0800
t.format("%m/%d/%Y %H:%M");
// 04/13/2023 09:45
```
### iso8601
> alias: [to_string](#to_string)
Return the time string with the [RFC3339] format.
```nvs
let t = time.parse("2023-04-13 09:45:26 +08:00");
t.iso8601;
// 2023-04-13T09:45:26+08:00
```
### to_string
> alias: [iso8601](#iso8601)
Return the time string with the [RFC3339] format.
## Time Format
| Code | Example | Description |
| ---- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `%a` | `Sun` | Weekday as locale’s abbreviated name. |
| `%A` | `Sunday` | Weekday as locale’s full name. |
| `%w` | `0` | Weekday as a decimal number, where 0 is Sunday and 6 is Saturday. |
| `%d` | `08` | Day of the month as a zero-padded decimal number. |
| `%b` | `Sep` | Month as locale’s abbreviated name. |
| `%B` | `September` | Month as locale’s full name. |
| `%m` | `09` | Month as a zero-padded decimal number. |
| `%y` | `13` | Year without century as a zero-padded decimal number. |
| `%Y` | `2013` | Year with century as a decimal number. |
| `%H` | `07` | Hour (24-hour clock) as a zero-padded decimal number. |
| `%I` | `07` | Hour (12-hour clock) as a zero-padded decimal number. |
| `%p` | `AM` | Locale’s equivalent of either AM or PM. |
| `%M` | `06` | Minute as a zero-padded decimal number. |
| `%S` | `05` | Second as a zero-padded decimal number. |
| `%f` | `000000` | Microsecond as a decimal number, zero-padded on the left. |
| `%z` | `+0000` | UTC offset in the form ±HHMM\[SS\[.ffffff\]\] (empty string if the object is naive). |
| `%Z` | `UTC` | Time zone name (empty string if the object is naive). |
| `%j` | `251` | Day of the year as a zero-padded decimal number. |
| `%U` | `36` | Week number of the year (Sunday as the first day of the week) as a zero padded decimal number. All days in a new year preceding the first Sunday are considered to be in week 0. |
| `%W` | `35` | Week number of the year (Monday as the first day of the week) as a decimal number. All days in a new year preceding the first Monday are considered to be in week 0. |
| `%c` | `Sun Sep 8 07:06:05 2013` | Locale’s appropriate date and time representation. |
| `%x` | `09/08/13` | Locale’s appropriate date representation. |
| `%X` | `07:06:05` | Locale’s appropriate time representation. |
| `%%` | `%` | A literal `%` character. |
[rfc3339]: https://tools.ietf.org/html/rfc3339
[time format]: #time-format
[unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
---
# Delete a file or directory
The [fs.remove_file](/stdlib/std.fs#remove_file) function is used to delete a file, the first argument is a string of the file path.
And the [fs.remove_dir](/stdlib/std.fs#remove_dir) function for use to delete a directory. [fs.remove_dir_all](/stdlib/std.fs#remove_dir_all) for delete a directory and all its sub-directories and files.
```nv, no_run
use std.fs;
fn main() throws {
try fs.remove_file("path/to/file.txt");
try fs.remove_dir("path/to/directory");
try fs.remove_dir_all("path/to/directory");
}
```
---
# Download a file
You can download a file from the server using the [`HttpClient.get`](/stdlib/std.net.http.client.HttpClient#method.get) method.
```nv,no_run
use std.net.http.client.HttpClient;
use std.net.http.OK;
use std.io;
use std.fs.File;
fn main() throws {
let f = try File.create("image.png");
defer try f.close();
let client = HttpClient.new();
let res = try client.get("https://httpbin.org/image/png");
if (res.status() != OK) {
println("Failed to download file", try res.text());
return;
}
let content_length = res.headers().get("Content-Length");
println("Downloaded file size:", content_length);
if (let body = res.body()) {
try io.copy(body, f);
}
}
```
After run `navi run main.nv`, we will download and save `image.png` file.
In this case:
- We use the [fs.create] to open a file with **WRITE** mode, if the file is not exists, it will create a new file.
- The `defer` statement is used to close the file after the function returns (Like defer in Go).
- We create an HTTP client using the [`HttpClient.new`](/stdlib/std.net.http.client.HttpClient#method.new) function.
- The [`HttpClient.get`](/stdlib/std.net.http.client.HttpClient#method.get) method is used to send a `GET` request to the server and get the response.
- Copy the response body to the file using the [`io.copy`](/stdlib/std.io#method.copy) function.
---
# Example: Echo Server
This example demonstrates a basic TCP echo server that binds to a local address and port, continuously accepts incoming connections, spawns a new Navi coroutine for each connection to handle reading and writing data.
Here is the overall example and we will break it down into pieces so that it is easy to understand.
```nv,no_run
use std.{io.{self, Bytes}, net.{Connection, TcpAddr, TcpConnection, TcpListener}};
fn handle_connection(conn: Connection) {
let buf = Bytes.new(len: 1024);
do {
loop {
let n = try conn.read(buf);
if (n == 0) {
break;
}
try conn.write_all(buf.slice(0, n));
}
} catch(e) {
println(`error: ${e.error()}`);
}
}
fn main() throws {
let listener = try TcpListener.bind("127.0.0.1:3000");
loop {
let conn = try! listener.accept();
spawn handle_connection(conn);
}
}
```
## Binding to an Address
```nv,ignore
let listener = try TcpListener.bind("127.0.0.1:3000");
```
The [`TcpListener.bind`]() method binds the TCP listener to the specified address (`127.0.0.1`) and port (`3000`). The `try` keyword is used to propagate any errors that occur during the binding process.
## Accepting Connections
```nv,ignore
loop {
let conn = try! listener.accept();
spawn handle_connection(conn);
}
```
The server enters an infinite loop to continuously accept incoming connections. For each accepted connection, it spawns a new Navi coroutine to handle the connection.
## Handling the Connection
```nv,ignore
fn handle_connection(conn: Connection) {
let buf = Bytes.new(len: 1024);
do {
loop {
let n = try conn.read(buf);
if (n == 0) {
break;
}
try conn.write_all(buf.slice(0, n));
}
} catch(e) {
println(`error: ${e.error()}`);
}
}
```
The `handle_connection` function takes a `Connection` object as an argument, representing the accepted connection. It reads data from the connection into a buffer, then writes the data back to the connection. The loop continues until the connection is closed by the client.
---
# For
For loop can traverse each element in the specified container. It will call the `iter` method of the container to create an iterator.
The following code for loop traverses all elements in the array. After the loop ends, `count` is equal to `45`.
```nvs
let count = 0;
for (let x in [number] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) {
count += x;
}
```
## Range
`range` type also implements the `iter` method. You can specify the `initial value`, `end value`, `step value` for it. The step value defaults to `1`.
The following code for loop also calculates `count` equal to `45`.
```nvs
let count = 0;
for (let x in 0..10) {
count += x;
}
```
And when you set the step value to `3`, the loop ends and `count` is equal to `18`.
```nvs
let count = 0;
for (let x in (0..10).step(3)) {
count += x;
}
```
::: warning
In a `for` loop, you can't use stateful functions, such as `ma`, `sum`.
:::
---
# For
For 循环可以遍历指定容器中的每个元素。它会调用容器的 `iter` 方法来创建一个迭代器。
下面的代码使用 `for` 循环遍历数组中的所有元素。循环结束后,`count` 等于 `45`。
```nvs
let count = 0;
for (let x in [number] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) {
count += x;
}
```
## Range
`range` 类型实现了 `iter` 方法。你可以为它指定 `初始值`、`结束值`、`步长`。步长默认为 `1`。
下面的代码创建了 `0` 到 `9` 的 range 类型,并用 `for` 迭代这个 range。循环结束后,`count` 等于 `45`。
```nvs
let count = 0;
for (let x in 0..10) {
count += x;
}
```
然后,你可以使用 `step` 方法来设置步长。下面的代码创建了一个 `0` 到 `9` 的 range 类型,并设置步长为 `3`。循环结束后,`count` 等于 `18`。
```nvs
let count = 0;
for (let x in (0..10).step(3)) {
count += x;
}
```
::: warning
在 `for` 循环中,不能使用状态函数,例如 `ma`、`sum`。
:::
---
# Function
## Define function
Navi Stream's function definition is very similar to Navi, also support keyword arguments.
```nvs
fn foo(count: number): number {
return count + 1;
}
let a = foo(1);
```
## Call a function
Here is an example of calling the `max` function in [math] to calculate the maximum value of `5` and `10`.
::: info
Checkout more [Stdlib] docs.
:::
```nvs
let a = math.max(5, 10);
```
::: info
There have a little different with Navi's function call, in Navi Stream, we can call a function without `()` like Ruby, but in Navi, you must use `()`.
:::
The following example calls the `open` function to get the opening price of the current period.
```nvs
let a = quote.open;
```
It same like:
```nvs
let a = quote.open();
```
[stdlib]: ../../stdlib/index.md
---
# Function
## 函数声明
Navi Stream 的函数定义与 Navi 非常相似,也支持 keyword arguments.
```nvs
fn foo(count: number): number {
return count + 1;
}
let a = foo(1);
```
## 函数调用
下面的例子调用 [math] 中的 `max` 函数来计算 `5` 和 `10` 的最大值。
::: info
更多内容,可以查阅 [Stdlib] 文档。
:::
```nvs
let a = math.max(5, 10);
```
::: info
在 Navi Stream 中,函数调用与 Navi 有一点不同,Navi Stream 中可以像 Ruby 一样在函数调用的时候省略 `()`,但在 Navi 中,必须写 `()`。
:::
下面的例子调用 `open` 函数来获取当前周期的开盘价。
```nvs
let a = quote.open;
```
也等于这样写:
```nvs
let a = quote.open();
```
[stdlib]: ../../stdlib/index.md
---
---
order: 2
---
# Getting Started
Navi Stream is a embed language in Navi, when you installed Navi, you can use Navi Stream.
## Quick Start
Create file named `macd.nvs`
::: info
Navi Stream file extension is `.nvs`, and Navi file extension is `.nv`.
:::
```nvs
use quote, ta;
param {
Length1 = 12,
Length2 = 26,
Length3 = 9,
}
let fast_ma = ema(close, Length1);
let slow_ma = ema(close, Length2);
export let hist = fast_ma - slow_ma;
export let signal = ema(hist, Length3);
export let macd = (hist - signal) * 2;
```
Make a sample JSON data named `data.json`, you can download this sample file: https://raw.githubusercontent.com/navi-language/navi/main/examples/macd/data.json
Then you can use it in Navi file, `main.nv`:
```nv, no_run
// Import `macd.nvs` file as module
use macd;
use std.fs;
use std.json;
struct Candlestick {
time: int,
open: float,
high: float,
low: float,
close: float,
volume: float,
turnover: float,
}
impl Candlestick {
fn to_string(): string {
return `{ time: ${self.time}, open: ${self.open}, high: ${self.high}, low: ${self.low}, close: ${self.close}, volume: ${self.volume}, turnover: ${self.turnover} }`;
}
}
fn main() throws {
let f = fs.open("data.json");
let data = json.parse(f.read_to_string());
let candlesticks: [Candlestick] = [];
for (let item in data.array()!) {
candlesticks.push(Candlestick {
time: item.get("time")?.int()!,
open: item.get("open")?.float()!,
high: item.get("high")?.float()!,
low: item.get("low")?.float()!,
close: item.get("close")?.float()!,
volume: item.get("volume")?.int()! as float,
turnover: item.get("turnover")?.int()! as float
});
}
// Create a Navi Stream instance
let t = macd.new();
for (let candlestick in candlesticks) {
// Execute Navi Stream
t.execute(time: candlestick.time, close: candlestick.close);
println(candlestick.to_string());
}
}
```
Then run it:
```bash
$ navi run main.nv
{ time: 943920000, open: 29.5, high: 29.8, low: 26.01, close: 26.4, volume: 3040519, turnover: 8408718336 }
{ time: 946512000, open: 26.31, high: 26.9, low: 24.5, close: 24.75, volume: 736270, turnover: 1889136896 }
{ time: 949017600, open: 24.98, high: 27.85, low: 23.75, close: 25.04, volume: 1965104, turnover: 4966612480 }
...
```
---
---
order: 2
---
# Getting Started
Navi Stream 是 Navi 中的嵌入式语言,当你安装 Navi 后,你就可以使用 Navi Stream。
## 快速开始
创建一个名为 `main.nv` 的文件:
::: info
Navi Stream 采用 `.nvs` 作为文件扩展名,而 Navi 采用 `.nv` 作为文件扩展名。
:::
```nvs
use quote, ta;
param {
Length1 = 12,
Length2 = 26,
Length3 = 9,
}
let fast_ma = ema(close, Length1);
let slow_ma = ema(close, Length2);
export let hist = fast_ma - slow_ma;
export let signal = ema(hist, Length3);
export let macd = (hist - signal) * 2;
```
我们来准备一些数据,创建一个名为 `data.json` 的文件,你可以下载这个示例文件:https://raw.githubusercontent.com/navi-language/navi/main/examples/macd/data.json
然后在 `main.nv` 文件中使用这个数据:
```nv, no_run
// Import `macd.nvs` file as module
use macd;
use std.fs;
use std.json;
struct Candlestick {
time: int,
open: float,
high: float,
low: float,
close: float,
volume: float,
turnover: float,
}
impl Candlestick {
fn to_string(): string {
return `{ time: ${self.time}, open: ${self.open}, high: ${self.high}, low: ${self.low}, close: ${self.close}, volume: ${self.volume}, turnover: ${self.turnover} }`;
}
}
fn main() throws {
let f = fs.open("data.json");
let data = json.parse(f.read_to_string());
let candlesticks: [Candlestick] = [];
for (let item in data.array()!) {
candlesticks.push(Candlestick {
time: item.get("time")?.int()!,
open: item.get("open")?.float()!,
high: item.get("high")?.float()!,
low: item.get("low")?.float()!,
close: item.get("close")?.float()!,
volume: item.get("volume")?.int()! as float,
turnover: item.get("turnover")?.int()! as float
});
}
// Create a Navi Stream instance
let t = macd.new();
for (let candlestick in candlesticks) {
// Execute Navi Stream
t.execute(time: candlestick.time, close: candlestick.close);
println(candlestick.to_string());
}
}
```
现在我们可以运行这个程序:
```bash
$ navi run main.nv
{ time: 943920000, open: 29.5, high: 29.8, low: 26.01, close: 26.4, volume: 3040519, turnover: 8408718336 }
{ time: 946512000, open: 26.31, high: 26.9, low: 24.5, close: 24.75, volume: 736270, turnover: 1889136896 }
{ time: 949017600, open: 24.98, high: 27.85, low: 23.75, close: 25.04, volume: 1965104, turnover: 4966612480 }
...
```
---
---
order: 30
---
# I18n
Navi support to define I18n strings.
Use `@` and follow by a identifier to define a I18n string.
The following statement defines the I18n string `hello`, which supports two languages: `zh-CN` and `en`.
```nvs
@hello {
"en" = "Hello",
"zh-CN" = "你好"
}
```
Yes, we can also use string interpolation to define I18n strings:
```nvs
let n = 10;
@length {
"en" = `Length {n}`,
"zh-CN" = `长度 {n}`
}
```
You can use `@` to reference I18n string with current locale.
For example, the following code assigns `Hello` or `你好` to the variable `value` when the closing price is greater than the opening price, otherwise it is an empty string.
```nvs
@hello {
"en" = "Hello",
"zh-CN" = "你好",
}
let value = quote.close > quote.open ? @hello : "";
```
::: warning
The string interpolation in I18n string definition is relative to the statement where the definition is referenced.
:::
For example, the following code assigns `Length 10` or `长度 10` to the variable `a`, and `Length 20` or `长度 20` to the variable `b`.
```nvs
@length {
"en" = `Length {n}`,
"zh-CN" = `长度 {n}`,
}
let n = 10;
let a = @length;
// a = "Length 10"
n = 20;
let b = @length;
// b = "Length 20"
```
Result:
```nvs
{
"a": "Length 10",
"b": "Length 20"
}
```
---
---
order: 30
---
# I18n
Navi 支持定义国际化字符串。
使用 `@` 后跟一个标识符来定义一个国际化字符串。
以下语句定义了一个名为 `hello` 的国际化字符串,它支持两种语言:`zh-CN` 和 `en`。
```nvs
@hello {
"en" = "Hello",
"zh-CN" = "你好"
}
```
是的,我们也可以使用字符串插值来定义国际化字符串:
```nvs
let n = 10;
@length {
"en" = `Length {n}`,
"zh-CN" = `长度 {n}`
}
```
你可以使用 `@<标识符>` 来引用当前语言环境的国际化字符串。
例如,以下代码在收盘价大于开盘价时将 `Hello` 或 `你好` 赋值给变量 `value`,否则为空字符串。
```nvs
@hello {
"en" = "Hello",
"zh-CN" = "你好",
}
let value = quote.close > quote.open ? @hello : "";
```
::: warning
国际化字符串定义中的字符串插值是相对于引用定义的语句。
:::
例如,以下代码将 `Length 10` 或 `长度 10` 赋值给变量 `a`,将 `Length 20` 或 `长度 20` 赋值给变量 `b`。
```nvs
@length {
"en" = `Length {n}`,
"zh-CN" = `长度 {n}`,
}
let n = 10;
let a = @length;
// a = "Length 10"
```
---
---
order: -99
---
# Identifier
Identifier is the name of a variable ([let]) or a function ([fn]). It must start with a letter or an underscore, followed by any number of letters, digits, or underscores.
## Valid example
```
var1
_var1
_var_1
_Var1
```
## Invalid example
```
1var
var 1
var-1
```
## Keywords
You must avoid using Navi Stream's keywords, otherwise it will cause a syntax error.
Here is keyword list (not including all), please follow the compiler's check result.
```
let
var
varip
nil
true
false
for
to
step
while
continue
break
if
else
fn
return
param
meta
export
import
use
switch
case
default
plot
```
[let]: statement/assign.md
[fn]: statement/function.md
---
# If
You can execute different code branches according to the condition.
Yes, our `if` statement is similar to the `if` statement in other programming languages.
We have `if ... else` and `if ... else if ... else` statements.
The following example assigns `value` to `1` if `close` is greater than `open`, otherwise `0`.
```nvs
use quote;
if (close > open) {
value = 1;
}
```
Use `else` for the branch to execute when the condition is not met.
```nvs
use quote;
let a = 1;
let b = 2;
if (close > open) {
a = 2 + a;
b = 1 + b;
} else {
b = 2 + b;
}
```
Use `else if` to execute different branches according to multiple conditions.
```nvs
if (close > open) {
value = 1;
} else if (close > prev_close) {
value = 2;
} else {
value = 3;
}
```
:::warning
In a `if` branches, you can't use stateful functions, such as `ma`, `sum`.
:::
---
# If
你可以根据条件执行不同的代码分支。
是的,我们的 `if` 语句与其他编程语言中的 `if` 语句类似。
我们有 `if ... else` 和 `if ... else if ... else` 语句。
下面的示例将 `value` 赋值为 `1`,如果 `close` 大于 `open`,否则赋值为 `0`。
```nvs
use quote;
if (close > open) {
value = 1;
}
```
用 `else` 来执行条件不满足时的分支。
```nvs
use quote;
let a = 1;
let b = 2;
if (close > open) {
a = 2 + a;
b = 1 + b;
} else {
b = 2 + b;
}
```
用 `else if` 来根据多个条件执行不同的分支。
```nvs
if (close > open) {
value = 1;
} else if (close > prev_close) {
value = 2;
} else {
value = 3;
}
```
:::warning
在 `if` 分支中,不能使用状态函数,例如 `ma`、`sum`。
:::
---
# Install Navi
If you are on Linux or macOS, you can install Navi by running the following command in your terminal:
```bash
curl -sSL https://navi-lang.org/install | bash
```
> This script is also used for upgrading.
The install script will install Navi into `~/.navi`.
And then add `~/.navi` to your `$PATH` environment variable.
After installing, you can run `navi -h` to check if it is installed successfully.
```bash
$ navi -h
```
::: tip
If `navi` is not found, you may need to restart your terminal to reload the `$PATH` environment variable.
Or just add `export PATH="$HOME/.navi:$PATH"` to your shell configuration file, and source it.
:::
## Install a specific version
You can install a specific version by passing the version number to the script.
::: code-group
```bash [Latest Nightly]
curl -sSL https://navi-lang.org/install | bash -s -- nightly
```
```bash [Special Version]
curl -sSL https://navi-lang.org/install | bash -s -- v0.9.0-nightly
```
:::
---
# Iterator
> Deprecated
The iterator is a special object that can be used to iterate over a collection of objects. It is used in the `for` loop.
## Usage
```nvs
let a = [number] { 1, 2, 3, 4, 5 };
for (let i in a) {
// i is 1, 2, 3, 4, 5
}
```
In this case, for actually is called `a.iter().next()` to get the iterator.
## Methods
### next
Returns the next value in the iterator, if there is no next value, it will return `nil`.
```nvs
let a = [number] { 1, 2, 3, 4, 5 };
let iter = a.iter();
while (iter.next()) {
// iter.value is 1, 2, 3, 4, 5
}
```
### has_next
Check is there a next value in the iterator. Return `true` when has next value, otherwise return `false`.
```nvs
let a = [number] { 1, 2 }.iter();
a.has_next(); // true
a.next(); // 1
a.has_next(); // true
a.next(); // 2
a.has_next(); // false
a.next(); // nil
```
### collect
Collect all the values in the iterator into a [Array].
```nvs
let a = [number] { 1, 2, 3, 4, 5 };
let iter = a.iter();
let b = iter.collect();
// b is [1, 2, 3, 4, 5]
let c = iter.collect();
// c is []
let d = iter.next();
// d is nil
```
[array]: ./array
---
---
layout: home
hero:
name: Navi
text: A high-performance programming language.
image:
light: /logo.svg
dark: /logo-dark.svg
alt: VitePress
actions:
- theme: brand
text: Get Started
link: /learn/
- theme: alt
text: Try Navi
link: https://navi-lang.org/play/
features:
- title: Simple and Clean Syntax
details: Designed with a straightforward and clean syntax.
- title: Modern Optional-Type and Error-Handling Design
details: With a modern design of optional types and error handling, Navi allows developers to gracefully manage exceptional cases and abnormal data.
- title: No NULL Pointer Panic, Safe Runtime
details: No NULL pointer exceptions. Once your code compiles, you can expect consistent and reliable execution.
---
## Install Navi
Run the following command in your terminal:
```sh
curl -sSL https://navi-lang.org/install | bash
```
## Quick Start
The following `main.nv` is a simple "Hello, World!" program in Navi:
```nv
fn main() throws {
println("Hello, World!");
}
```
Run the program with the following command:
```sh
$ navi run
```
[Continue learning](/learn/) about Navi.
---
---
order: -98
---
# Literal
## bool
The `bool` is a built-in type, you can use it to define a variable, `true` and `false` is boolean value.
```nvs
let a: bool = true;
let b = false;
```
## number
In Navi Stream, we use `number` for all numeric values, including integer and floating point numbers.
```nvs
let a: number = 1;
let a = 3.1415;
```
## string
We can use double quotes `"` and `` ` `` to create a string literal.
```nvs
let a = "hello world";
let b: string = `你好世界`;
```
## nil
`nil` is a special value, it means nothing, it is used to represent a null value.
```nvs
let a = nil;
```
## color
Unlike most programming languages, we have a `color` type in Navi Stream, which is used to represent colors for chart drawing.
It is like CSS syntax, we can use `#` to define a color, and then use HEX color or predefined color names like `red`, `blue`, `green` that CSS supports.
::: info
You can free to use any color, Navi Stream will not process the color, it will output the color to the chart directly.
:::
```nvs
let a: color = #ff00ff;
let b = #red;
```
---
# Make a HTTP Client
In some cases, you may need to make multiple requests to the same server. In such cases, it is more efficient to create an HTTP client and reuse it for multiple requests. The [`HttpClient`](/stdlib/std.net.http.client.HttpClient) struct provides a way to create an HTTP client that can be reused for multiple requests.
The HTTP Client holds a connection pool to reuse the connections, so it is more efficient than creating a new connection for each request.
And the client also provides a way to set more complex options like `enable_redirect`, `user_agent` for us to control the behavior of the client. See: [`HttpClient.new`](/stdlib/std.net.http.client.HttpClient#method.new) for more details.
## Create a HTTP client
```nv,no_run
use std.net.http.client.{HttpClient, Request};
use std.net.http.OK;
fn main() throws {
let client = HttpClient.new(
max_redirect_count: 5,
user_agent: "navi-client",
);
let req = try Request.get("https://httpbin.org/get");
let res = try client.request(req);
if (res.status() != OK) {
try println("Failed to fetch repo", res.text());
return;
}
try println(res.text());
}
```
In the above example:
1. We create an HTTP client using the [`HttpClient.new`](/stdlib/std.net.http.client.HttpClient#method.new) function.
2. We set the `max_redirect_count` to `5`, and `user_agent` to `navi-client`.
- The `max_redirect_count` is the maximum number of redirects to follow.
- The `user_agent` is the `User-Agent` header to send with the request.
3. We create a [`Request`](stdlib/std.net.http.client.Request) object using the [`Request.get`](/stdlib/std.net.http.client.Request#method.get) method and set the URL of the GitHub API.
4. Then we send the request using the [`HttpClient.request`](/stdlib/std.net.http.client.HttpClient#method.request) method.
---
---
order: 1
---
# math
The `math` package provides a series of mathematical functions.
| Function Name | Description |
|---------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
| abs(n: `number`): `number` | Returns the absolute value of `n` |
| acos(n: `number`): `number` | Returns the arccosine of `n` |
| asin(n: `number`): `number` | Returns the arcsine of `n` |
| atan(n: `number`): `number` | Returns the arctangent of `n` |
| ceiling(n: `number`): `number` | Returns the smallest integer greater than or equal to `n` |
| floor(n: `number`): `number` | Returns the largest integer less than or equal to `n` |
| round(n: `number`): `number` | Rounds `n` to the nearest integer |
| cos(n: `number`): `number` | Returns the cosine of `n` |
| exp(n: `number`): `number` | Returns `e` raised to the power of `n` |
| fracpart(n: `number`): `number` | Returns the fractional part of `n` |
| intpart(n: `number`): `number` | Returns the integer part of `n` |
| ln(n: `number`): `number` | Returns the natural logarithm (base `e`) of `n` |
| log(n: `number`): `number` | Returns the logarithm (base 10) of `n` |
| sin(n: `number`): `number` | Returns the sine of `n` in radians |
| sqrt(n: `number`): `number` | Returns the square root of `n` |
| tan(n: `number`): `number` | Returns the tangent of `n` |
| max(n1: `number?`, n2: `number?`, ...): `number` | Returns the largest value among the input parameters |
| min(n1: `number?`, n2: `number?`, ...): `number` | Returns the smallest value among the input parameters |
| mod(n: `number`): `number` | Returns the remainder of `a` divided by `b` |
| pow(n: `number`): `number` | Returns `a` raised to the power of `b` |
| reverse(n: `number`): `number` | [Deprecated] Changes the sign of `n` |
| sgn(n: `number`): `number`, sign(n: `number`): `number` | [Deprecated] Returns the sign of `n`. If `n > 0`, returns `1`; if `n = 0`, returns `0`; if `n < 0`, returns `-1` |
| isnil(n: `number`): `bool` | Returns `true` if `n` is `NAN`, `+INF`, or `-INF`; otherwise, returns `false` |
---
---
order: 1
---
# Math
`math` 包提供了一系列的数学函数。
| 函数名 | 描述 |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| abs(n: `number`): `number` | 返回 `n` 的绝对值 |
| acos(n: `number`): `number` | 返回 `n` 的反余弦值 |
| asin(n: `number`): `number` | 返回 `n` 的反正弦值 |
| atan(n: `number`): `number` | 返回 `n` 的反正切值 |
| ceiling(n: `number`): `number` | 返回大于或等于 `n` 的最小整数 |
| floor(n: `number`): `number` | 返回小于或等于 `n` 的最大整数 |
| round(n: `number`): `number` | 将 `n` 四舍五入到最近的整数 |
| cos(n: `number`): `number` | 返回 `n` 的余弦值 |
| exp(n: `number`): `number` | 返回 `e` 的 `n` 次方 |
| fracpart(n: `number`): `number` | 返回 `n` 的小数部分 |
| intpart(n: `number`): `number` | 返回 `n` 的整数部分 |
| ln(n: `number`): `number` | 返回 `n` 的自然对数(底数为 `e`) |
| log(n: `number`): `number` | 返回 `n` 的对数(底数为 10) |
| sin(n: `number`): `number` | 返回 `n` 的正弦值(单位为弧度) |
| sqrt(n: `number`): `number` | 返回 `n` 的平方根 |
| tan(n: `number`): `number` | 返回 `n` 的正切值 |
| max(n1: `number?`, n2: `number?`, ...): `number` | 返回输入参数中的最大值 |
| min(n1: `number?`, n2: `number?`, ...): `number` | 返回输入参数中的最小值 |
| mod(n: `number`): `number` | 返回 `a` 除以 `b` 的余数 |
| pow(n: `number`): `number` | 返回 `a` 的 `b` 次方 |
| reverse(n: `number`): `number` | [已弃用] 改变 `n` 的符号 |
| sgn(n: `number`): `number`, sign(n: `number`): `number` | [已弃用] 返回 `n` 的符号。如果 `n > 0`,返回 `1`;如果 `n = 0`,返回 `0`;如果 `n < 0`,返回 `-1` |
| isnil(n: `number`): `bool` | 如果 `n` 是 |
---
# Meta
`meta` use for define the metadata of this Navi Stream. It just like a doc of this script.
When the script is compiled, you can get the original array value of the specified name from `Instance`, which is usually used to attach some description information to the metric.
## Syntax
```nvs
meta {
title = "MACD indicator",
description = "MACD is a trend-following indicator that consists of three lines, namely the fast line, the slow line and the bar line."
author = "Navi Team"
}
```
---
# Meta
`meta` 用于定义 Navi Stream 的元数据。它类似于此脚本的文档。
当 Navi Stream 代码编译以后,你可以从 `Instance` 中获取指定名称的原始数组值,通常用于将一些描述信息附加到指标上。
## 语法格式
```nvs
meta {
title = "MACD indicator",
description = "MACD is a trend-following indicator that consists of three lines, namely the fast line, the slow line and the bar line."
author = "Navi Team"
}
```
---
---
order: 1
sidebar_label: 'Introduction'
---
- [English Version](/navi-stream/)
- [中文版本](/zh-CN/navi-stream/)
# Navi Stream
Navi Stream is a embed language in Navi, which is used to perform complex calculations and analysis based on market data.
## Features
- High performance: Navi Stream is also a static type, compiled language.
- Easy to use: Same sytax as Navi, easy to learn.
- Stream computing: Support stream computing, which can be used for real-time analysis.
- Cross-platform: Support running on Linux, Windows, macOS, etc., support iOS, Android, and run on Browser via WASM.
## Which scenarios can Navi Stream be used?
- As a market monitoring, by writing complex calculation logic, you can implement various market monitoring functions (alarm, decision, data construction).
- As a market monitoring client, by integrating SDK, write a real-time observation market data program, and make complex decisions (such as alarm, trading, etc.).
- Used in market charts, can be used as real-time market computing and draw on charts.
---
---
order: 1
sidebar_label: 'Navi Stream 介绍'
---
- [English Version](/navi-stream/)
- [中文版本](/zh-CN/navi-stream/)
# Navi Stream
Navi Stream 是 Navi 中的嵌入式语言,用于基于市场数据进行复杂计算和分析。
## 功能特点
- 高性能:Navi Stream 也是静态类型、编译型语言。
- 易用性:与 Navi 语法相同,易学易用。
- 流式计算:支持流式计算,可用于实时分析。
- 跨平台:支持在 Linux、Windows、macOS 等平台运行,支持 iOS、Android,通过 WebAssembly 支持在浏览器中运行。
## Navi Stream 的应用场景
- 作为市场监控,通过编写复杂计算逻辑,可以实现各种市场监控功能(报警、决策、数据构建)。
- 作为市场监控客户端,通过集成 SDK,编写实时观察市场数据程序,做出复杂决策(如报警、交易等)。
- 用于行情图表,可用于实时市场数据计算,并图表上绘制。
---
---
title: Syntax
order: 2
---
# Navi Stream Syntax
Navi Stream's syntax is mostly the same as Navi, but there are some differences, because they are have a little different in language design.
::: tip
- `.nvs` is the **Navi Stream** file extension.
- `.nv` is the **Navi** file extension.
:::
## Example
Here is a simple example of Navi Stream for MACD:
```nvs
// meta for describe how of this script todo (Like a file doc).
meta {
title = "MACD",
overlay = false
}
// Input parameters
param {
@meta(title = "短周期", range = 1..250)
fast_length = 12,
@meta(title = "长周期", range = 1..250)
slow_length = 26,
@meta(title = "移动平均周期", range = 1..250)
signal_length = 26
}
// Define a function
fn test(a: number): number {
return a + 1;
}
for (let i = 0 to 5) {
if (i % 2 == 0) {
a = a + i;
}
}
let fast_ma = ta.sma(quote.close, fast_length, 1);
let slow_ma = ta.sma(quote.close, slow_length, 1);
let macd = fast_ma - slow_ma;
let dea = ta.sma(macd, signal_length, 1);
let dif = macd - dea;
// Render result to chart
plot(macd, title = "MACD", color = #yellow);
plot(dea, title = "DEA", color = #blue);
plot(dif, title = "DIF", color = quote.close > quote.open ? #up : #down, style = plotstyle.histogram);
```
---
---
title: 语法
order: 2
---
# Navi Stream 语法
Navi Stream 的语法大部分与 Navi 相同,但是有一些差异,因为它们在语言设计上有一些不同。
::: tip
- `.nvs` 是 **Navi Stream** 文件的扩展名。
- `.nv` 是 **Navi** 文件的扩展名。
:::
## 示例
以下是一个关于 MACD 的 Navi Stream 简单示例:
```nvs
// 元数据用于描述此脚本的执行方式(类似于文件文档)。
meta {
title = "MACD",
overlay = false
}
// 输入参数
param {
@meta(title = "短周期", range = 1..250)
fast_length = 12,
@meta(title = "长周期", range = 1..250)
slow_length = 26,
@meta(title = "移动平均周期", range = 1..250)
signal_length = 26
}
// 定义一个函数
fn test(a: number): number {
return a + 1;
}
for (let i = 0 to 5) {
if (i % 2 == 0) {
a = a + i;
}
}
let fast_ma = ta.sma(quote.close, fast_length, 1);
let slow_ma = ta.sma(quote.close, slow_length, 1);
let macd = fast_ma - slow_ma;
let dea = ta.sma(macd, signal_length, 1);
let dif = macd - dea;
// 将结果渲染到图表
plot(macd, title = "MACD", color = #yellow);
plot(dea, title = "DEA", color = #blue);
plot(dif, title = "DIF", color = quote.close > quote.open ? #up : #down, style = plotstyle.histogram);
```
---
[[toc]]
# Navi Tools
## Cli Commands
Navi has a command line tool `navi`, you can use it to run, test, and benchmark your Navi code.
Use `navi -h` to see the help.
### navi run
Use `navi run` to run a Navi file, default it will run `main.nv` in the current directory.
```shell
$ navi run
```
Or you can pass a file name to run it.
```shell
$ navi run main.nv
```
Or use `-s` to run a script.
```shell
navi run -s 'use std.io; io.println("Hello World!");'
```
### navi test
Use `navi test` to run tests in a Navi file, default it will run all tests (\*_/_.{nv,nvs}) in the current directory.
```shell
$ navi test
```
Or you can pass a file name to run it.
```shell
$ navi test foo/bar.nv
```
### navi bench
Use `navi bench` to run benchmarks in a Navi file, default it will run all benchmarks (\*.{nv,nvs}) in the current directory.
::: warning
Unlike `navi test`, `navi bench` will not iter all subdirectories, it will only run benchmarks in the current directory.
:::
```shell
$ navi bench
```
Or you can pass a file name to run it.
```shell
$ navi bench foo/bar.nv
```
### navi fmt {#fmt}
`navi fmt` is a code formatter for Navi, it can format your code to a standard style.
```bash
$ navi fmt -h
Format all Navi files (*.nv)
Usage: navi fmt [OPTIONS] [INPUT]
Arguments:
[INPUT] File (.nv) or path to format [default: .]
Options:
-e, --emit Emit formatted code to stdout or write to file [default: files] [possible values: files, stdout]
-s, --stdin Read code from stdin
-t, --type Whether to read Navi code or Navi Stream code [default: nv] [possible values: nv, nvs]
-h, --help Print help information
```
## Editor Support
### VS Code
Navi has an VS Code extension, which can help you to write Navi code.
https://marketplace.visualstudio.com/items?itemName=huacnlee.navi

### Zed
https://github.com/navi-language/zed-navi
We have a [Zed extension for Navi](https://github.com/navi-language/zed-navi), just open you Zed Extensions and search `navi`, you will find it.

### Navi LSP Server {#lsp}
Navi LSP Server is a [Language Server Protocol] for Navi and Navi Stream, it can be used in any IDEs that support LSP.
The `navi-lsp-server` bin is included in the release package: when you [install](/installation) Navi, you will get the `navi-lsp-server` bin.
```bash
$ navi-lsp-server &
Starting Navi LSP server
```
### Tree Sitter
We have [tree-sitter](https://tree-sitter.github.io/) support for Navi and Navi Stream, you can use it in any editor that supports tree-sitter.
- [tree-sitter-navi](https://github.com/navi-language/tree-sitter-navi)
- [tree-sitter-navi-stream](https://github.com/navi-language/tree-sitter-navi-stream)
### tmLanguage
There have a `tmLanguage` file of Navi and Navi Stream for syntax highlight.
- [navi.tmLanguage](https://github.com/navi-language/navi-language.github.io/blob/main/.vitepress/navi.tmLanguage.json)
- [navi-stream.tmLanguage](https://github.com/navi-language/navi-language.github.io/blob/main/.vitepress/navi-stream.tmLanguage.json)
## GitHub Actions
We have a GitHub Action for Navi, you can use it like this:
```yaml
- uses: navi-language/setup-navi@v1
```
If you want a special Navi version:
```yml
- uses: navi-language/setup-navi@v1
with:
navi-version: 0.9.0
```
Visit [navi-language/setup-navi] to learn more.
[Language Server Protocol]: https://microsoft.github.io/language-server-protocol/
[navi-language/setup-navi]: https://github.com/navi-language/setup-navi
---
# navi-website
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.0.11. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
---
---
order: -99
---
# Number
In Navi Stream, `number` type to define a int or float number.
::: info
In internal, `number` actually is not a object, it's a primitive type. But we will plan to change it to a object in the future.
:::
```nvs
let a = 1;
let b = 2;
let c = a + b + 3;
// 6
c.to_string();
// "6"
```
## Methods
### to_string
Convert a number to a string.
```nvs
let a = 1;
a.to_string();
// "1"
```
---
---
order: 2
---
# Object
---
---
order: 2
---
# Object
---
---
order: 8
---
# OpenTrade
OpenTrade is a type of object that represents a open market position.
## Methods
### size
`size(): number`
Returns the size of the open trade.
Returns the direction and the number of contracts traded in the open trade. If the value is > 0, the market position was long. If the value is < 0, the market position was short.
### entry_price
`entry_price(): number`
Returns the entry price of the open trade.
### entry_time
`entry_time(): datetime`
Returns the entry time of the open trade.
### profit
`profit(): number`
Returns the profit/loss of the open trade. Losses are expressed as negative values.
### profit_percent
`profit_percent(): number`
Returns the profit/loss of the open trade, expressed as a percentage of the initial capital. Losses are expressed as negative values.
### max_runup
`max_runup(): number`
Returns the maximum run-up of the open trade, i.e., the maximum possible profit during the trade.
### max_runup_percent
`max_runup_percent(): number`
Returns the maximum run-up of the open trade, i.e., the maximum possible profit during the trade, expressed as a percentage and calculated by formula: `Highest Value During Trade / (Entry Price x Quantity)`.
### max_drawdown
`max_drawdown(): number`
Returns the maximum drawdown of the open trade, i.e., the maximum possible loss during the trade.
### max_drawdown_percent
`max_drawdown_percent(): number`
Returns the maximum drawdown of the open trade, i.e., the maximum possible loss during the trade, expressed as a percentage and calculated by formula: `Lowest Value During Trade / (Entry Price x Quantity)`.
### commission
`commission(): number`
Returns the sum of entry and exit fees paid in the open trade.
---
---
order: 0
---
# Operator
Like as many programming languages, Navi Stream supports basic arithmetic and logical operators.
Navi Stream also follows the precedence of traditional programming languages, so you can continue to use it in Navi Stream syntax according to your previous programming habits.
```nvs
let a = 100 + 2 - 10 * 5 / 2 % 3;
// 101
```
## Operators
| Operator | Description |
| ----------------------------- | ---------------------------------------- |
| `!a` | Bitwise or logical complement |
| `a + b` | Arithmetic addition |
| `a += b` | Arithmetic addition and assignment |
| `-a` | Arithmetic negation |
| `a - b` | Arithmetic subtraction |
| `a -= b` | Arithmetic subtraction and assignment |
| `a * b` | Arithmetic multiplication |
| `a *= b` | Arithmetic multiplication and assignment |
| `a / b` | Arithmetic division |
| `a /= b` | Arithmetic division and assignment |
| `a % b` | Arithmetic remainder |
| `a %= b` | Arithmetic remainder and assignment |
| `a < b` | Less than comparison |
| `a <= b` | Less than or equal to comparison |
| `a = 1` | Assignment/equivalence |
| `a == b` | Equality comparison |
| `a > b` | Greater than comparison |
| `a >= b` | Greater than or equal to comparison |
| `a != n` | Nonequality comparison |
| `a && b` | `AND` logical |
| a || b
| `OR` logical |
| `expr.ident` | Member access |
| `a[n]` | Ref preview `n` period data |
## Ref preview data
We can use `quote.close[n]` to reference the data of the previous.
For example, we have data of K line (1m):
| idx | time | close |
| --- | ----- | ----- |
| 1 | 10:00 | 10.25 |
| 2 | 10:01 | 10.50 |
| 3 | 10:02 | 10.75 |
| 4 | 10:03 | 11.00 |
| 5 | 10:04 | 11.25 |
If now we at the period of idx 5:
- `quote.close[0]` is the current data.
- `quote.close[1]` is the data of the previous 1 period, value is `11.00`.
- `quote.close[2]` is the data of the previous 2 period, value is `10.75`.
- `quote.close[3]` is the data of the previous 3 period, value is `10.50`.
---
# Param
`param` use for define external parameters for Navi Stream, external parameters can be passed in when instantiating, thus changing the behavior of the script.
## Syntax
```nvs
param {
value = 10,
message = "hello",
@meta(title = "Limit", range = 50..100)
limit = 50,
@meta(title = "Render style", choices = ["line" = "线", "bar" = "柱"])
render_style = "line",
};
```
## Meta decorator
Use `@meta` decorator to define the options of this parameter.
### Value Range
**NOTE: Only `number` type parameters can specify a range.**
The following code specifies that the value of parameter `a` must be **greater than or equal to** `50` and **less than or equal to** `100`, default is `50`.
```nvs
param {
@meta(range = 50..100)
a = 50,
};
```
### choices
`choices` is used to specify the available values for a parameter (Like a enum).
The following code specifies that the value of parameter `a` must be one of `3`, `6`, `9`, default is `3`.
```nvs
param {
@meta(choices = [3, 6, 9])
a = 3,
}
```
You can also specify a name for each choice, which is used to give users a clearer parameter description in the client.
```nvs
param {
@meta(choices = ["短" = 3, "中" = 6, "长" = 9])
a = 6,
}
```
### title
以下语句为 `length` 参数指定了标题 `长度`。
```nvs
param {
@meta(title = "长度")
length = 10,
}
```
---
# Param
`param` 用于定义 Navi Stream 的外部参数,外部参数可以在实例化时传入,从而改变脚本的行为。
## Syntax
```nvs
param {
value = 10,
message = "hello",
@meta(title = "Limit", range = 50..100)
limit = 50,
@meta(title = "Render style", choices = ["line" = "线", "bar" = "柱"])
render_style = "line",
};
```
## Meta decorator
用 `@meta` 装饰器来定义参数的选项。
### Value Range
**NOTE: 只有 `number` 类型的参数支持 `range` 选项。**
下面的代码指定参数 `a` 的值必须**大于等于** `50` 且**小于等于** `100`,默认值为 `50`。
```nvs
param {
@meta(range = 50..100)
a = 50,
};
```
### choices
`choices` 用于指定参数的可选值(类似于枚举)。
下面的代码指定参数 `a` 的值必须是 `3`, `6`, `9` 中的一个,如果没有指定,则默认值为 `3`。
```nvs
param {
@meta(choices = [3, 6, 9])
a = 3,
}
```
你也可以为每个选项指定一个名称,这样在客户端中可以给用户提供更清晰的参数描述。
```nvs
param {
@meta(choices = ["短" = 3, "中" = 6, "长" = 9])
a = 6,
}
```
### title
以下语句为 `length` 参数指定了标题 `长度`。
```nvs
param {
@meta(title = "长度")
length = 10,
}
```
---
---
order: 3
---
# quote
| Function Name | Description |
|------------------------------------------------------|-----------------------------------------------------------------------------------------|
| open: `number`, o: `number` | Opening price of the current K-line |
| high: `number`, h: `number` | Highest price of the current K-line |
| low: `number`, l: `number` | Lowest price of the current K-line |
| close: `number`, c: `number` | Closing price of the current K-line |
| volume: `number`, vol: `number`, v: `number` | Trading volume of the current K-line |
| amount: `number`, vola: `number`, turnover: `number` | Trading amount of the current K-line |
| isup: `bool` | Returns `true` if the current K-line is rising |
| isdown: `bool` | Returns `true` if the current K-line is falling |
| isequal: `bool` | Returns `true` if the current K-line is equal |
| date: `number` | Returns the date of the current K-line or specified timestamp in the format of `YYMMDD` |
| year: `number` | Returns the year of the current K-line or specified timestamp |
| month: `number` | Returns the month of the current K-line or specified timestamp |
| day: `number` | Returns the day of the current K-line or specified timestamp |
| time: `number` | Returns the time of the current K-line or specified timestamp in the format of `HHMMSS` |
| hour: `number` | Returns the hour of the current K-line or specified timestamp |
| minute: `number` | Returns the minute of the current K-line or specified timestamp |
| second: `number` | Returns the second of the current K-line or specified timestamp |
| weekday: `number` | Returns the weekday of the current K-line or specified timestamp, `0` represents Sunday |
| weekofyear: `number` | Returns the week of the year of the current K-line or specified timestamp |
---
---
order: 3
---
# Quote [WIP]
::: warning
此功能仍在开发中。我们可能会在未来更改语法。
:::
| 函数名 | 描述 |
| ---------------------------------------------------- | ------------------------------------------------- |
| open: `number`, o: `number` | 当前 K 线的开盘价 |
| high: `number`, h: `number` | 当前 K 线的最高价 |
| low: `number`, l: `number` | 当前 K 线的最低价 |
| close: `number`, c: `number` | 当前 K 线的收盘价 |
| volume: `number`, vol: `number`, v: `number` | 当前 K 线的交易量 |
| amount: `number`, vola: `number`, turnover: `number` | 当前 K 线的交易金额 |
| isup: `bool` | 如果当前 K 线上涨,返回 `true` |
| isdown: `bool` | 如果当前 K 线下跌,返回 `true` |
| isequal: `bool` | 如果当前 K 线平盘,返回 `true` |
| date: `number` | 返回当前 K 线或指定时间戳的日期,格式为 `YYMMDD` |
| year: `number` | 返回当前 K 线或指定时间戳的年份 |
| month: `number` | 返回当前 K 线或指定时间戳的月份 |
| day: `number` | 返回当前 K 线或指定时间戳的日期 |
| time: `number` | 返回当前 K 线或指定时间戳的时间,格式为 `HHMMSS` |
| hour: `number` | 返回当前 K 线或指定时间戳的小时 |
| minute: `number` | 返回当前 K 线或指定时间戳的分钟 |
| second: `number` | 返回当前 K 线或指定时间戳的秒数 |
| weekday: `number` | 返回当前 K 线或指定时间戳的星期几,`0` 代表星期日 |
| weekofyear: `number` | 返回当前 K 线或指定时间戳的年份中的周数 |
---
# Range
Range is a special object that allows you to create a range of values. It is useful for creating a list of numbers, or a list of characters.
## Usage
```nvs
let a = 1..5;
// a is contains [1, 2, 3, 4], not 5
let iter = a.iter();
iter.next(); // 1
iter.next(); // 2
iter.next(); // 3
iter.next(); // 4
iter.next(); // nil
```
## Methods
### collect
Return a [Array] that contains all the values in the range.
```nvs
let a = 1..5;
export let b = a.collect();
// b is [1, 2, 3, 4]
```
### iter
Return an [Iterator] that can be used to iterate over the range.
```nvs
let a = 1..5;
let iter = a.iter();
iter.next(); // 1
```
Or you can use the `for` loop to iterate over the range.
```nvs
for (let i in 1..5) {
// i is 1, 2, 3, 4
}
```
### step
Return a new `Range` that contains the values in the range with the given step.
```nvs
let a = 1..5;
export let b = a.step(2).collect();
// b is [1, 3]
for (let i in 1..5.step(2)) {
// i is 1, 3
}
```
---
# Read a file as stream
The `File.read` method is used to read a file in stream mode. The first argument accpets a `std.io.Bytes` as a buffer to store the read data.
You must special each read operation to handle the buffer size and the read data. For example 1024 bytes buffer size, and read until the end of the file.
Every call `read` method it will return the actual read bytes length, if it's 0, it means the end of the file. So you can use it to break the loop.
## Navi Code
```nv, no_run
use std.fs.File;
use std.io.Bytes;
fn main() throws {
let file = try File.open("path/to/file.txt");
loop {
let chunk = Bytes.new(len: 1024);
// Read 1024 bytes to chunk, if the end of the file, it will return 0
if (try file.read(chunk) == 0) {
break;
}
// Do something with buf
print(chunk.to_string());
}
}
```
---
# Read a file to bytes
The `fs.read` function is used to read a file and return a `std.io.Bytes`. The first argument is the file path.
To read the `std.io.Bytes` to a UTF-8 string, you can use the `to_string` method.
::: warning
The valid UTF-8 bytes will be converted to string, and invalid UTF-8 bytes, it will be replaced with U+FFFD, e.g. �
:::
Or use [encode_to_string](/stdlib/std.io#Bytes#encode_to_string) method to convert to a string with a specific encoding.
## Navi Code
```nv,no_run
use std.fs;
fn main() throws {
let buf = try fs.read("path/to/file.txt");
// buf is a std.io.Bytes
// Convert buf to UTF-8 string
let content = buf.to_string();
}
```
---
# Read a file to string
The `fs.read_to_string` function is used to read a file and return a `string`.
## Navi Code
```nv, no_run
use std.fs;
fn main() throws {
let content = try fs.read_to_string("path/to/file.txt");
}
```
---
# Read a JSON file
The `fs.open` function is used to open a file and return a `std.fs.File` instance, in
The `json.from_reader` function is used to parse a JSON string from a reader and return a `T` instance base on the `T` type parameter.
::: info
The `std.fs.File` is has implemented the `std.io.Read` interface,
so you can use it for `json.from_reader` function.
:::
## Navi Code
```nv, no_run
use std.json;
use std.fs.File;
struct User {
name: string,
id: int,
profile: Profile?
}
struct Profile {
city: string?
}
fn main() throws {
let file = try File.open("path/to/user.json");
let user = try json.parse::(file);
}
```
If we have a `user.json`:
```json
{
"name": "Alice",
"id": 42,
"profile": { "city": "New York" }
}
```
---
# Read file
---
# Rename a file
The [fs.rename](/stdlib/std.fs#rename) function is used to rename a file.
This is more like the `mv` command in Unix-like systems.
The first argument is a string of the source file path, and the second argument is a string of the destination file path.
```nv, no_run
use std.fs;
fn main() throws {
try fs.rename("path/to/source.txt", "path/to/destination.txt");
}
```
---
# Return
`return` use for function [fn] return a value and break the function. `return` can only appear in the function.
```nvs
fn foo(count: number): number {
return count + 1;
}
```
[fn]: function.md
---
# Return
`return` 语句用于函数 [fn] 返回一个值并终止函数。`return` 只能出现在函数中。
```nvs
fn foo(count: number): number {
return count + 1;
}
```
[fn]: function.md
---
# Send a HTTP request
The [`HttpClient`](/stdlib/std.net.http.client.HttpClient) object provides a HTTP client to send a HTTP request to a server.
## Send a GET request
```nv,no_run
use std.net.http.Headers;
use std.net.http.client.{HttpClient, Request};
use std.net.http.OK;
struct Repo {
id: int,
name: string,
full_name: string,
// We can define the default value for the field.
// here is means the private default value is false.
private: bool = false,
html_url: string,
description: string,
}
const GITHUB_API = "https://api.github.com";
fn main() throws {
let client = HttpClient.new(default_headers: Headers.from_map({
"User-Agent": "Navi",
"Accept": "application/vnd.github.v3+json",
}));
let req = try Request.get(`${GITHUB_API}/repos/navi-language/navi`).set_query({
"t": "hello",
});
let res = try client.request(req);
if (res.status() != OK) {
println("Failed to fetch repo", try res.text());
return;
}
let repo = try res.json::();
println(`${repo.name} - ${repo.description}`);
}
```
In the above example, we send a `GET` request to the GitHub API to fetch the `navi-language/navi` repository information.
- We create a [`HttpClient`](/stdlib/std.net.http.client.HttpClient) object with default headers. The `User-Agent` header is used to identify the client making the request. The `Accept` header is used to specify the media type of the response that the client can understand. The `Accept` header is set to `application/vnd.github.v3+json` to request the GitHub API to return the response in the `v3` version of the GitHub API.
- We create a [`Request`](/stdlib/std.net.http.client.Request) object using the [`Request.get`](/stdlib/std.net.http.client.Request#method.get) method and set the URL of the GitHub API. We also set the query parameters using the [Request.set_query](/stdlib/std.net.http.client.Request#method.set_query) method. The query parameters are used to send additional data with the request.
- If the request is successful, we parse the response JSON into a `Repo` struct and print the repository name and description. We use [`Response.json`](/stdlib/std.net.http.client.Response#method.json) method on the Response type to parse the JSON response.
> This is same as [`json.parse`](/stdlib/std.json#parse) method, but it is more convenient to use.
>
> ```nv, ignore
> use std.json;
> let repo = try json.parse::(res.text())
> ```
- If the request fails, we print the error message.
After running the program, you should see the repository name and description printed on the console.
```txt
navi - https://github.com/navi-language/navi
```
::: warning NOTE
- The [`Response.json`](/stdlib/std.net.http.client.Response#method.json) method is a generic method, so you must specify the type by use `::` syntax (This is the same as [`json.parse`](/stdlib/std.json#parse) method).
- The `res.text` and `res.json` methods can throw an error, so you should use the `try` keyword to handle the error.
- The response body is streamed, so you can't read it multiple times. The `text` and `json` methods are consuming the response body, so you can't call them multiple times.
:::
## Send a POST request
```nv,no_run
use std.net.http.client.{HttpClient, Request};
use std.net.http.{Headers, Created};
use std.json;
struct Repo {
id: int,
name: string,
full_name: string,
private: bool = false,
html_url: string,
description: string,
}
struct CreateRepo {
org: string,
repo: string,
has_issues: bool,
}
fn main() throws {
let client = HttpClient.new();
let payload = CreateRepo {
org: "navi-language",
repo: "new-repo",
has_issues: true,
};
let req = try Request.post("https://api.github.com/repos")
.set_headers(Headers.from_map({
"Authorization": "Bearer ",
}))
.set_json(payload);
let res = try client.request(req);
if (res.status() != Created) {
println("Failed to create repo", try res.text());
return;
}
let repo = try res.json::();
println(`Repo ${repo.name} created successfully`);
println(repo.html_url);
}
```
As you see in the above example, we send a POST request to create a new repository on GitHub API.
- We create a [`HttpClient`](/stdlib/std.net.http.client.HttpClient) object.
- We create a `CreateRepo` struct to represent the request body. The struct contains the organization name, repository name, and whether the repository has issues enabled.
- We create a [`Request`](/stdlib/std.net.http.client.Request) object using the [`Request.post`](/stdlib/std.net.http.client.Request#method.post) method and set the URL of the GitHub API. We set the `Authorization` header to authenticate the request using a GitHub token. We set the request body using the [Request.set_json](/stdlib/std.net.http.client.Request#method.set_json) method. The `set_json` method serializes the struct to a JSON string and sets the `Content-Type` header to `application/json`.
- If the request is successful, we parse the response JSON into a `Repo` struct and print the repository name and URL. We use the [`Response.json`](/stdlib/std.net.http.client.Response#method.json) method on the Response type to parse the JSON response.
---
# Send a URL-encoded form
The `www-form-urlencoded` is a common format for sending data to the server. You can use the [`Request.set_form`](/stdlib/std.net.http.client.Request#method.set_form) method to create a `URL-encoded` form request.
```nv,no_run
use std.net.http.client.{HttpClient, Request};
use std.net.http.OK;
fn main() throws {
let client = HttpClient.new();
let form = {
"name": "Navi",
"website": "https://navi-lang.org",
"profile[bio]": "Navi is a programming language",
};
let req = try Request.post("https://httpbin.org/post").set_form(form);
let res = try client.request(req);
if (res.status() != OK) {
println("Failed to send form", try res.text());
return;
}
println(try res.text());
}
```
Run the above code with `navi run main.nv`, will output:
```json
{
"form": {
"email": "huacnlee@gmail.com",
"name": "Jason Lee"
},
"headers": {
"Accept": "*/*",
"Content-Length": "41",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org"
},
"url": "https://httpbin.org/post"
}
```
In this case, we using the [`Request.set_form`](/stdlib/std.net.http.client.Request#method.set_form) method to set the form data to the request, and the `Content-Type` will be set to `application/x-www-form-urlencoded` automatically.
- The [`Request.set_form`](/stdlib/std.net.http.client.Request#method.set_form) method accepts a `map` or `struct` type that will be serialized to the `www-form-urlencoded` format.
- Please note that the `FormUrlEncoded` just accept 1 level key-value pair, if you want to send a nested form, if you prefer to send a nested form, the request will throw an error. If you want to send a nested form, you special the key with `[]` to make it as an array, like `profile[bio]` in the example above.
---
---
order: 4
---
# Set
Set for storage a set of unique collection of items.
## Usage
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.insert("c");
items.insert("a");
items.len();
// 3
items.contains("a");
// true
```
Same behavior as [Array], we can init a set with a different type.
```nvs
// init a number set
let items = set.new_number();
// init a string set
let items = set.new_string();
// init a boolean set
let items = set.new_bool();
// init a color set
let items = set.new_color();
```
## Methods
### insert
Insert an item into the set.
- If the set did not previously contain this value, `true` is returned.
- If the set already contained this value, `false` is returned.
```nvs
let items = set.new_string();
items.insert("a");
// true
items.insert("b");
// true
items.insert("a");
// false
items.len();
// 2
```
### remove
Remove an item from the set, return `true` if the item was removed, otherwise `false`.
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.len();
// 2
items.remove("a");
// true
items.len();
// 1
items.remove("a");
// false
```
### contains
Check if the set contains an item.
```nvs
let items = set.new_string();
items.insert("a");
items.contains("a");
// true
items.contains("b");
// false
```
### len
Return the number of items in the set.
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.len();
// 2
```
### clear
Remove all items from the set.
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.len();
// 2
items.clear();
items.len();
// 0
```
### iter
Return a [Iterator] for the set.
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
let iter = items.iter();
iter.next();
// "a"
iter.next();
// "b"
```
### to_array
Convert the set to an array.
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.to_array();
// ["a", "b"]
```
---
---
order: 4
---
# Statement
---
---
order: 4
---
# Statement
---
# Stdlib
Navi have provided a set of standard libraries that can be used to build your own Navi application.
These libraries are designed to be used in a modular way, so you can pick and choose the parts that you need.
## Table of Content
---
---
order: 3
---
# Stdlib
Navi Stream 内置了一系列的标准库函数,用于快速开发策略。
你可以直接以 `.` 的形式调用标准库中的函数,例如 `math.sin`。
或者你可以使用 `use ` 来导入整个包,然后直接调用函数,例如 `use math;`,然后你就可以在 Navi Stream 中直接使用整个 [Math](./math.md) 库中的函数,例如:`sign`,`abs`...
## 示例
```nvs
use math;
let a = 10;
let b = max(a, 100);
```
或者你可以直接调用 `math.max`:
```nvs
let a = 10;
let b = math.max(a, 100);
```
---
---
order: 3
---
# StdLib
Navi Stream built-in a series of standard library functions for quick development of strategies.
You can directly call functions in the standard library in the form of `.`, for example `math.sin`.
Or you can use `use ` to import the entire package and then call the function directly, for example `use math;`, then you can use the functions in the entire [Math](./math.md) library directly in Navi Stream, such as: `sign`, `abs`...
## Example
```nvs
use math;
let a = 10;
let b = max(a, 100);
```
Or you can directly to call `math.max`:
```nvs
let a = 10;
let b = math.max(a, 100);
```
---
---
order: 5
---
# strategy
## METADATA: initial_cash
`initial_cash: number`
Initial cash.
Default is `100000`.
## METADATA: allow_entry_in
`allow_entry_in: direction`
Used to specify in which market direction the `strategy.entry` function is allowed to open positions. Possible values: `direction.long`, `direction.short`, `nil`.
Default is `nil`.
## METADATA: commission
`commission: string`
Commission for each transaction.
The format is `type=value`, where `type` is one of `percent`, `fixed`, `cash-per-contract`.
You can use multiple commissions by separating them with a comma (e.g., `percent=0.1,fixed=10`).
Default is ``.
## METADATA: initial_margin_long
Margin rate for initial short positions.
Default is `1.0`.
## METADATA: initial_margin_short
Margin rate for initial short positions.
Default is `1.0`.
## METADATA: maintenance_margin_long
Margin rate for maintenance long positions.
Default is `1.0`.
## METADATA: maintenance_margin_short
Margin rate for maintenance short positions.
Default is `1.0`.
## METADATA: slippage
`slippage: number`
Slippage for each transaction.
Default is `0`.
## METADATA: risk_free_rate
`risk_free_rate: number`
Risk-free rate of return is the annual percentage change in the value of an investment with minimal or zero risk. It is used to calculate the [Sharpe](https://en.wikipedia.org/wiki/Sharpe_ratio) and [Sortino](https://en.wikipedia.org/wiki/Sortino_ratio) ratios.
Default is `0.02`.
## entry
`entry(id: string, side: direction, qty: number = nil, qty_percent: number = nil, limit: number = nil, stop: number = nil, remark: string = nil)`
Creates a new order to open or add to a position.
If the call does not contain `limit` or `stop` arguments, it creates a `market order`.
If the call specifies a `limit` value but no `stop` value, it places a `limit order` that executes after the market price reaches the `limit` value or a better price (lower for buy orders and higher for sell orders).
If the call specifies a `stop` value but no `limit` value, it places a `stop order` that executes after the market price reaches the `stop` value or a worse price (higher for buy orders and lower for sell orders).
If the call contains `limit` and `stop` arguments, it creates a `stop-limit` order, which generates a `limit order` at the `limit` price only after the market price reaches the `stop` value or a worse price.
Orders from this command, unlike those from `strategy.order`, are affected by the `pyramiding` parameter. Pyramiding specifies the number of concurrent open entries allowed per position. For example, with `pyramiding = 3`, the strategy can have up to three open trades, and the command cannot create orders to open additional trades until at least one existing trade closes.
When a strategy executes an order from this command in the opposite direction of the current market position, it reverses that position.
For example, if there is an open long position of five shares, an order from this command with a `quantity` of 5 and a direction of `direction.short` triggers the sale of 10 shares to close the long position and open a new five-share short position.
Users can change this behavior by specifying an allowed direction with the `METADATA: allow_entry_in`.
## order
`order(id: string, side: direction, qty: number = nil, qty_percent: number = nil, limit: number = nil, stop: number = nil, remark: string = nil)`
Creates a new order to open, add to, or exit from a position.
If the call does not contain `limit` or `stop` arguments, it creates a `market order`.
If the call specifies a `limit` value but no `stop` value, it places a `limit order` that executes after the market price reaches the `limit` value or a better price (lower for buy orders and higher for sell orders).
If the call specifies a `stop` value but no `limit` value, it places a `stop order` that executes after the market price reaches the `stop` value or a worse price (higher for buy orders and lower for sell orders).
If the call contains `limit` and `stop` arguments, it creates a `stop-limit` order, which generates a `limit order` at the `limit` price only after the market price reaches the `stop` value or a worse price.
Orders from this command, unlike those from `strategy.entry`, are not affected by the `pyramiding` parameter. Strategies can open any number of trades in the same direction with calls to this function.
This command does not automatically reverse open positions because it does not exclusively create entry orders like `strategy.entry` does.
For example, if there is an open long position of five shares, an order from this command with a `quantity` of 5 and a direction of `direction.short` triggers the sale of five shares, which closes the position.
## close
`close(id: string, qty: number = nil, qty_percent: number = nil, remark: string = nil)`
Creates an order to exit from the part of a position opened by entry orders with a specific identifier. If multiple entries in the position share the same ID, the orders from this command apply to all those entries, starting from the first open trade, when its calls use that ID as the id argument.
This command always generates market orders.
## cancel
`cancel(id: string)`
Cancels a pending or unfilled order with a specific identifier. If multiple unfilled orders share the same ID, calling this command with that ID as the id argument cancels all of them. If a script calls this command with an id representing the ID of a filled order, it has no effect.
## cancel_all
`cancel_all()`
Cancels all pending or unfilled orders, regardless of their identifiers.
This command is most useful when working with price-based orders (e.g., limit orders).
## initial_cash
`initial_cash(): number`
Returns the amount of initial capital set in the strategy properties.
## equity
`equity(): number`
Returns current equity `(strategy.initial_cash + strategy.net_profit + strategy.open_profit)`.
## position_size
`position_size(): number`
Returns the direction and size of the current market position. If the value is > 0, the market position is long. If the value is < 0, the market position is short.
## avg_trade
`avg_trade(): number`
Returns the average amount of money gained or lost per trade. Calculated as the sum of all profits and losses divided by the number of closed trades.
## avg_trade_percent
`avg_trade_percent(): number`
Returns the average percentage gain or loss per trade. Calculated as the sum of all profit and loss percentages divided by the number of closed trades.
## net_profit
`net_profit(): number`
Returns the total currency value of all completed trades.
## net_profit_percent
`net_profit_percent(): number`
Returns the total value of all completed trades, expressed as a percentage of the initial capital.
## gross_profit
`gross_profit(): number`
Returns the total currency value of all completed winning trades.
## gross_profit_percent
`gross_profit_percent(): number`
Returns the total currency value of all completed winning trades, expressed as a percentage of the initial capital.
## gross_loss
`gross_loss(): number`
Returns the total currency value of all completed losing trades.
## gross_loss_percent
`gross_loss_percent(): number`
Returns the total currency value of all completed losing trades, expressed as a percentage of the initial capital.
## open_profit
`open_profit(): number`
Returns the total currency value of all open trades.
## max_runup
`max_runup(): number`
Returns the maximum equity run-up value for the whole trading interval.
## max_runup_percent
`max_runup_percent(): number`
Returns the maximum equity run-up value for the whole trading interval, expressed as a percentage and calculated by formula: `Highest Value During Trade / (Entry Price x Quantity)`.
## max_drawdown
`max_drawdown(): number`
Returns the maximum equity drawdown value for the whole trading interval.
## max_drawdown_percent
`max_drawdown_percent(): number`
Returns the maximum equity drawdown value for the whole trading interval, expressed as a percentage and calculated by formula: `Lowest Value During Trade / (Entry Price x Quantity)`.
## max_held_all
`max_held_all(): number`
Returns the maximum number of contracts/shares/lots/units in one trade for the whole trading interval.
## max_held_long
`max_held_long(): number`
Returns the maximum number of contracts/shares/lots/units in one long trade for the whole trading interval.
## max_held_short
`max_held_short(): number`
Returns the maximum number of contracts/shares/lots/units in one short trade for the whole trading interval.
## position_avg_price
`position_avg_price(): number`
Returns the average entry price of current market position. If the market position is flat, `nil` is returned.
## win_trades
`win_trades(): number`
Returns number of profitable trades for the whole trading interval.
## loss_trades
`loss_trades(): number`
Returns number of losing trades for the whole trading interval.
## open_trades
`open_trades(): [opentrade]`
Returns an array of open trades.
## closed_trades
`closed_trades(): [closedtrade]`
Returns an array of closed trades.
## margin_liquidation_price
`margin_liquidation_price(): number`
When margin is used in a strategy, returns the price point where a simulated margin call will occur and liquidate enough of the position to meet the margin requirements.
---
---
order: 2
---
# String
We can use double quotes `"` and `` ` `` to create a string literal.
```nvs
let a = "hello world";
let b: string = `你好世界`;
```
## String Interpolation
We can use `${}` to interpolate a expression into a string, you must use backticks `` ` `` to create a string literal.
```nvs
let a = 100;
let b = `hello ${a + 2}`;
// b = "hello 102"
```
## Methods
### to_number
Convert a string to a number.
```nvs
let a = "100";
a.to_number();
// 100
let b = "3.1415";
b.to_number();
// 3.1415
```
### to_lowercase
Convert a string to lowercase.
```nvs
let a = "Hello World";
a.to_lowercase();
// "hello world"
```
### to_uppercase
Convert a string to uppercase.
```nvs
let a = "Hello World";
a.to_uppercase();
// "HELLO WORLD"
```
### substring
Get a substring from a string.
```nvs
let a = "Hello World";
a.substring(0, 5);
// "Hello"
```
### replace
Replace all matches substring in a string.
```nvs
let a = "Hello World";
let b = a.replace("Hello", "Hi");
// b is "Hi World"
a = "Hello World";
b = a.replace("l", "L");
// b is "HeLLo WorLd"
```
### len
Return number of chars in a string.
```nvs
let a = "你好 Navi Stream 🌈";
a.len();
// 9
```
### contains
Check if a string contains a substring.
```nvs
let a = "Hello World";
a.contains("Hello");
// true
a.contains("hello");
// false
```
### starts_with
Check if a string starts with a substring.
```nvs
let a = "Hello World";
a.starts_with("Hello");
// true
a.starts_with("foo");
// false
```
### ends_with
Check if a string ends with a substring.
```nvs
let a = "Hello World";
a.ends_with("World");
a.ends_with("foo");
// false
```
### split
Split a string into a list of strings.
```nvs
let a = "Hello World";
let b = a.split(" ");
// b is ["Hello", "World"]
b.len()
// 2
```
### trim
Trim whitespace from the start and end of a string.
```nvs
let a = " Hello World ";
let b = a.trim();
// b is "Hello World"
```
### trim_start
Trim whitespace from the start of a string.
```nvs
let a = " Hello World ";
let b = a.trim_start();
// b is "Hello World "
```
### trim_end
Trim whitespace from the end of a string.
```nvs
let a = " Hello World ";
let b = a.trim_end();
// b is " Hello World"
```
### insert
> Deprecated, this will remove.
Insert a string to this `String` at a byte offset and returns a new string.
```nvs
let a = "Hello World";
let b = a.insert(5, " Navi Stream");
// a is "Hello World"
// b is "Hello Navi Stream World"
```
### push
> Deprecated, this will remove.
Push a string to this `String` at the end and returns a new string.
```nvs
let a = "Hello";
let b = a.push(" World");
// a is "Hello"
// b is "Hello World"
```
---
---
order: 6
---
# Struct
Like Navi, Navi Stream support struct.
## Struct definition
```nvs
struct QuoteInfo {
symbol: string,
price: number,
volume: number
}
```
## Struct initialization
```nvs
let quote_info = QuoteInfo {
symbol: "AAPL",
price: 100.0,
volume: 1000
}
// Mutate struct
quote_info.symbol = "MSFT"
quote_info.price = 200.0
```
---
# Switch
If you have many conditions, using `switch` statement is more convenient and clear.
```nvs
let a = 1;
let b = 0;
switch (a) {
case 1:
b = 5;
case 2:
b = 10;
case 3:
b = 20;
default:
b = 30;
}
```
It same like this [if] statement:
```nvs
let a = 1;
let b = 0;
if (a == 1) {
b = 5;
} else if (a == 2) {
b = 10;
} else if (a == 3) {
b = 20;
} else {
b = 30;
}
```
[if]: ./if.md
---
# Switch
我们可以用 `switch` 语句来代替多个 `if` 语句,处理多种条件的场景。
```nvs
let a = 1;
let b = 0;
switch (a) {
case 1:
b = 5;
case 2:
b = 10;
case 3:
b = 20;
default:
b = 30;
}
```
上面的代码等同于下面的代码:
```nvs
let a = 1;
let b = 0;
if (a == 1) {
b = 5;
} else if (a == 2) {
b = 10;
} else if (a == 3) {
b = 20;
} else {
b = 30;
}
```
[if]: ./if.md
---
---
order: 2
---
# ta
`ta` package provides some common technical analysis functions.
## between & range
`between(a: number, b: number, c: number): bool`
> alias: range
When `a` is between `b` and `c`, return `true`, otherwise return `false`.
```nvs
ta.between(3, 1, 4);
// true
ta.between(0, 1, 4);
// false
```
## cross
`cross(a: number, b: number): bool`
When `a` crosses `b` from below, return `true`, otherwise return `false`.
## longcross
`longcross(a: number, b: number, n: number): bool`
When `a` is less than `b` for `n` periods, and `a` crosses `b` from below this period, return `true`, otherwise return `false`.
## valuewhen
`valuewhen(x: bool, n: any): any`
When `x` is `true`, return `n`'s current value, otherwise return `n`'s previous value.
## switch
`switch(x1: bool, n1: any, x2: bool, n2: any, ...): any`
When `x1` is `true`, return `n1`, when `x2` is `true`, return `n2`, and so on.
## orelse
`orelse(x: bool, y: any): any`
When `x` is valid, return `x`, otherwise return `y`.
## all
`all(x: number, n: number): bool`
When `n` periods of `x` are all `true`, return `true`, otherwise return `false`. `n` is `0` means from the first valid value.
## any
`any(x: number, n: number): bool`
When `n` periods of `x` have at least one `true`, return `true`, otherwise return `false`. `n` is `0` means from the first valid value.
## barslast
`barslast(x: bool): number`
Get the current period number since the **last** time `x` is `true`.
## barssince
`barssince(x: bool): number`
Get the current period number since the **first** time `x` is `true`.
## count
`count(x: bool, n: number): number`
Get the number of times `x` is `true` in `n` periods. `n` is `0` means from the first valid value.
## dma
`dma(x: number, n: number): number`
Get the dynamic moving average of `x`.
## ema
`ema(x: number, n: number): number`
Get the `n` period exponential moving average of `x`.
## filter
`filter(x: number, n: number): bool`
Return `true` when `x` is `true` and its previous `n` periods are all `false`.
## hhv
`hhv(x: number, n: number): number`
Get the maximum value of `x` in `n` periods. `n` is `0` means from the first valid value.
## hhvbars
`hhvbars(x: number, n: number): number`
Get `n` periods of `x` maximum value to the current period number. `n` is `0` means from the first valid value.
## llv
`llv(x: number, n: number): number`
Return the minimum value of `x` in `n` periods.
## llvbars
`llvbars(x: number, n: number): number`
Get `n` periods of `x` minimum value to the current period number. `n` is `0` means from the first valid value.
## sum
`sum(x: number, n: number): number`
Get the sum of `x` in `n` periods. `n` is `0` means from the first valid value.
## mular
`mular(x: number, n: number): number`
Get the product of `x` in `n` periods. `n` is `0` means from the first valid value.
## ref
`ref(x: number, n: number): number`
Reference the `x` value of `n` periods ago.
## ma
`ma(x: number, n: number): number`
Get `n` period simple average of `x`. `n` is `0` means from the first valid value.
## mema
`mema(x: number, n: number): number`
Get `n` period modified exponential moving average of `x`.
## sma
`sma(x: number, n: number): number`
Get `n` period simple moving average of `x`.
## tma
`tma(x: number, n: number): number`
Get `n` period triangular moving average of `x`.
## wma
`wma(x: number, n: number): number`
Get `n` period weighted moving average of `x`.
## barscount
`barscount(x: number)`
Get the current period number since the first valid value of `x`.
## last
`last(x: bool): number`
Get the last valid value of `x`.
## sumbars
`sumbars(a: number, b: number): number`
Get the period number of `a` plus `b` until `a` is greater than or equal to `b`.
## hod
`hod(x: number, n: number): number`
Get the maximum value of `x` in `n` periods.
## lod
`lod(x: number): number`
Get the minimum value of `x` in `n` periods.
## avedev
`avedev(x: number, n: number): number`
Get the average absolute deviation of `x` in `n` periods.
## devsq
`devsq(x: number, n: number): number`
Get the sum of the squares of the deviations of `x` in `n` periods.
## forcast
`forcast(x: number, n: number): number`
Get the `n` period linear regression forecast value of `x`.
## slope
`slope(x: number, n: number): number`
Get the `n` period linear regression slope of `x`.
## std
`std(x: number, n: number): number`
Get the `n` period standard deviation of `x`.
## stddev
`stddev(x: number, n: number): number`
Get the `n` period standard deviation of `x`.
## stdp
`stdp(x: number, n: number): number`
Get the `n` period standard deviation of `x`.
## var
`var(x: number, n: number): number`
Get the `n` period sample variance of `x`.
## varp
`varp(x: number, n: number): number`
Get the `n` period total variance of `x`.
## covar
`covar(x: number, y: number, n: number): number`
Get the `n` period covariance of `x` and `y`.
## relate
`relate(x: number, y: number, n: number): number`
Get the `n` period correlation coefficient of `x` and `y`.
## sar
`sar(n: number, s: number, m: number): number`
Get the parabolic SAR of `n` periods, `s` is the step length, `m` is the maximum step length.
## sarturn
`sarturn(n: number, s: number, m: number): number`
Get the parabolic SAR of `n` periods, `s` is the step length, `m` is the maximum step length. If the SAR turns up, return `1`, if the SAR turns down, return `-1`, otherwise return `0`.
---
---
order: 2
---
# Ta
`ta` 包提供了一些常见的技术分析函数。
## between & range
`between(a: number, b: number, c: number): bool`
> 别名:range
当 `a` 在 `b` 和 `c` 之间时,返回 `true`,否则返回 `false`。
```nvs
ta.between(3, 1, 4);
// true
ta.between(0, 1, 4);
// false
```
## cross
`cross(a: number, b: number): bool`
当 `a` 从下方穿过 `b` 时,返回 `true`,否则返回 `false`。
## longcross
`longcross(a: number, b: number, n: number): bool`
当 `a` 在 `n` 个周期内小于 `b`,并且在这个周期 `a` 从下方穿过 `b` 时,返回 `true`,否则返回 `false`。
## valuewhen
`valuewhen(x: bool, n: any): any`
当 `x` 为 `true` 时,返回 `n` 的当前值,否则返回 `n` 的前一个值。
## switch
`switch(x1: bool, n1: any, x2: bool, n2: any, ...): any`
当 `x1` 为 `true` 时,返回 `n1`,当 `x2` 为 `true` 时,返回 `n2`,依此类推。
## orelse
`orelse(x: bool, y: any): any`
当 `x` 有效时,返回 `x`,否则返回 `y`。
## all
`all(x: number, n: number): bool`
当 `x` 的 `n` 个周期都为 `true` 时,返回 `true`,否则返回 `false`。`n` 为 `0` 表示从第一个有效值开始。
## any
获取 `x` 和 `y` 的 `n` 期协方差。
## relate
`relate(x: number, y: number, n: number): number`
获取 `x` 和 `y` 的 `n` 期相关系数。
## sar
`sar(n: number, s: number, m: number): number`
获取 `n` 期的抛物线 SAR,`s` 是步长,`m` 是最大步长。
## sarturn
`sarturn(n: number, s: number, m: number): number`
获取 `n` 期的抛物线 SAR,`s` 是步长,`m` 是最大步长。如果 SAR 向上转,返回 `1`,如果 SAR 向下转,返回 `-1`,否则返回 `0`。
---
---
title: Pkgs
editLink: false
aside: false
---
---
---
title: Releases
editLink: false
---
## [v0.14.0](https://github.com/navi-language/navi/releases/tag/v0.14.0)
### Language
- Add support multi-case switch statement.
```rs
switch(a) {
case 1, 2:
println("a");
case 10, 20:
println("b");
default:
println("c");
}
```
- Enum literals can omit the type when the type can be inferred.
```rs
enum Color {
Red,
Green,
Blue,
}
let color: Color = .Red;
fn value(color: Color): int {
// ...
}
value(.Red);
```
### Stdlib
- Add `std.crypto.AssociatedOid` interface.
- Add `std.crypto.aes`, `std.crypto.chacha20`, `std.crypto.ecdsa`, `std.crypto.ed25519`, `std.jwt` modules.
- Add `std.crypto.BlockPadding`, `std.math.BigInt`.
- Add `std.io.Bytes.chunks` method.
- Add `std.io.Bytes.push`, `std.io.Bytes.pop` methods.
### Internal
- Fixed `switch` statement maybe caused JIT to not work. (tests\compile-err\switch\passed\nested.nv)
## [v0.13.0](https://github.com/navi-language/navi/releases/tag/v0.13.0)
### Language
- Add support for keyword arguments use in generic functions.
- Cast to the source type
```nv
type A = int;
type B = A;
let a: A = 1 as A;
let b: B = 1 as B;
let n = b as int; // cast to the source type
```
- Add support `x?.(type)` syntax
```nv
let a: Any? = 10;
assert_eq a?.(int), 10;
```
- Add support import global var
```nv
import std.net.http.NotFound;
let status = NotFound;
```
### Stdlib
- Remove `std.json.from_string`, `std.json.from_bytes`, `std.json.from_reader` methods, use `std.json.parse` instead.
- Remove `std.xml.from_string`, `std.xml.from_bytes`, `std.xml.from_reader` methods, use `std.xml.parse` instead.
- Remove `std.yaml.from_string`, `std.yaml.from_bytes`, `std.yaml.from_reader` methods, use `std.yaml.parse` instead.
- Improve `std.process`, `std.regex`.
- Rename `std.decimal.Decimal.from_string` to `std.decimal.Decimal.parse`.
- Use `std.template` to render file list in `std.net.http.server.FileSystem`.
- Add support custom filter/function/tester for `std.template`.
- Move `std.io.StringBuffer` to `std.str.StringBuffer`.
- Rename `std.io.StringBuffer.push` to `std.io.StringBuffer.push_string`.
- Add `std.io.StringBuffer.push`, `std.io.StringBuffer.pop` methods.
- Add `StatusCode.is_ok`, `StatusCode.is_success`, `StatusCode.is_client_error`, `StatusCode.is_server_error`, `StatusCodeis_redirection` methods.
- Improve `string.bytes` for better performance.
- Remove `std.base64.decode_to_string`, `std.base64.encode_bytes`, `std.base64.encode_string` methods.
- Add `std.base64.Base64Encoder`, `std.base64.Base64Decoder`.
- Add `std.net.http.CookieKey.from_bytes`, `std.net.http.CookieKey.bytes` methods.
- Add support for serialize/deserialize `std.io.Bytes`.
- Rename `std.time.DateTime.now` to `std.time.DateTime.now_utc`.
- Remove `std.time.DateTime.timezone` method.
- Add `std.time.DateTime.offset` method to get the offset from UTC in seconds.
- Change the argument of `std.time.DateTime.to_offset` method' to UTC seconds.
- Remove `std.time.parse`, `std.time.now`, `std.time.from_timestamp` functions.
- Add `std.time.TimeZone`.
- Add `std.time.DateTime.to_timezone` method to convert the `DateTime` to a specific timezone.
- Add `std.net.http.Status.as_int`, `std.net.http.Status.from_int` methods.
- Improve `std.regex` to remove Navi wrapping code.
- Improve `std.env` to remove Navi wrapping code.
- Improve `std.fs` to remove Navi wrapping code.
- Remove `std.fs.open`, `std.fs.create`, `std.fs.write_bytes` functions.
- Change the return value of `std.fs.glob` and `std.fs.read_dir` functions to a iterator.
- Remove `std.fs.Metadata.new` method use `std.fs.metadata` instead.
- Add `std.hex` module.
- Remove `std.io.Bytes.encode_to_string` method.
- Add `std.rand.RandReader`.
- Rename `std.io.Bytes.concat` to `std.io.Bytes.append`.
- Rework `std.crypto`.
- Add `Hash` interface.
- Add `md4`, `md5`, `sha1`, `sha2`, `ripemd`, `sha3`, `blake2`, `blake3` sub modules.
- Add `hmac`, `rsa` sub modules.
### Internal
- Alloc defer closure in the function stack.
- Improve pass generic params to the native functions.
## [v0.12.0](https://github.com/navi-language/navi/releases/tag/v0.12.0)
### Language
- Add support for convert error types with try statement.
- Add support for method as static closure.
- Add support for implicit conversion of an interface to its parent interface.
- Add support for cast an interface to its parent interface.
- Add support for unescape unicode sequences.
- Add support for `let else` statement.
```rs
let a: int? = 10;
let b = a {
panic "unreachable";
}
assert_eq b, 10;
```
- Add support for `try?` for expressions that don't return a value.
- Add support for accessing char at specific positions in a string using the `s[x]` syntax.
```rs
let s = "hello";
assert_eq s[0], 'h';
assert_eq s[1], 'e';
```
- Add support for custom iterator.
- Add support for destructuring assignment.
```rs
struct Point {
x: int,
y: int,
}
let p = Point {1, 2};
let Point { x, y } = p;
assert_eq x, 1;
assert_eq y, 2;
```
### Stdlib
- Add `std.io.Seek` interface.
- Add `std.io.pipe` method to creates a synchronous in-memory pipe.
- Implement `Debug` for `std.io.Bytes`, `std.decimal.Decimal`.
- Add `std.compress`, `std.mime`, `std.net.http`, `std.net.http.date`, `std.net.http.server`, `std.net.http.client.websocket`, `std.template` modules.
- Rework `std.net`, `std.net.http.client` modules.
- Fix deserialize to union type.
- Add update time component methods.
- Add `from` argument to `std.str.string.find` method.
- Rename `std.path.base` function to `std.path.file_name` and return `string?`.
- Rename `std.path.dir` function to `std.path.parent` and return `string?`.
- Add `std.fs.read_dir` function.
- Add `std.io.Read.take`, `std.io.ReadClose.take_close` methods.
### Pkg
- Add `parquet` module.
### Tools
- Print warnings in `run`/`compile` commands.
## [v0.11.0](https://github.com/navi-language/navi/releases/tag/v0.11.0)
### Language
- Now we published release for Windows platform.
- Improved the Closure syntax to support write in one line.
```rs
let s: string? = "hello";
// Before
s.map(|x| {
return `${x} world`;
});
// After
s.map(|x| `${x} world`);
```
- `spawn`, `defer` now support without block.
```rs
spawn println("hello");
defer println("world");
```
- And with this support, we have to changed empty map initialization from `{}` to `{:}`.
- Add `#[track_caller]` annotation for function, see also: [Track Caller](https://navi-lang.org/learn/#track-caller)
```rs
##[track_caller]
fn assert_success(value: bool) {
assert value == true;
}
test "assert_success" {
assert_success(true);
assert_success(false);
}
```
Will output:
```
error: thread 'thread 1#' at 'assertion failed: value == true', test.nv:8
stack backtrace:
0: test#0()
at test.nv:8
```
- Improve init array with rest expr:
```rs
let a = [1, 2, 3];
let b = [..a, 4, 5];
```
- Add to support unescape in `char`.
```rs
let c = '\n';
```
### Stdlib
- Add `std.net.tcp` module, and here is a guides: [Echo Server](https://navi-lang.org/guides/net/example-echo-server)
- Add `std.fs.copy`.
- Add `std.io.BufReader`, `std.io.BufWriter`.
- Add `flush` method to `std.io.Write` interface.
- Add `std.str.Debug` to create debug string with Debug interface.
```rs
struct MyType {}
impl Debug for MyType {
fn inspect(self): string {
return "MyType";
}
}
let my_type = MyType{};
assert_eq `${my_type:?}`, "MyType";
```
- Add `std.backtrace` module to get backtrace info.
- Add `std.env.join_paths` method.
- Improve `std.net.http`, removed `form`, `multipart` argument, let `body` support more types.
- Improve array's `unique`, `reverse`, `sort`, `sort_by`, `resize`, `truncate`, `clear` to return it self.
- Imporve `std.process` to add Command, `process.run` now returns `Child` instance.
- Imporve `std.fs`, `std.process` internal handle for better performance.
- Improve `std.path.join` to supports arbitrary argument: `path.join("a", "b", "c", "d")`.
- Improve `std.crypto`, let `update` and `hmac` function to support `string | Bytes`.
- Fix path join to use `push` avoid crate `Path` object.
- Rename `std.url.URL` -> `std.url.Url`.
### Pkg
- Done with `longport` package.
### Tools
We have rewrote `navi test`, `navi doc` command, and build a new stdlib docs.
https://navi-lang.org/stdlib/std.crypto
Now, there have more details in the docs, and we will keep improving it.
- `navi doc` is rewritten with new output format for better to generate docs.
- Improve `navi test` with parallel test running and we optimized the test result output.
```
test main2.nv 1/1 ok 0ms
test main.nv 2/2 ok 0ms
```
- Improve `navi test --doc` to show the correct line number in Markdown file.
- Add Navi info print when run `navi test`, `navi bench` command.
```
Navi 0.11.0 (x86_64-apple-darwin, a76be5f7, 2024-06-07 01:12:17 +08:00)
```
- Temporary workaround for `apple-darwin-aarch64` to print backtrace.
### Other changes
- fmt: leave comma in multi-line array_list.
- fmt: if there is no code in a closure, just an empty {}, no line breaks.
- fmt: Fix missed the function attributes.
- lsp: use custom display_value_type only for inlay hint.
- lsp: call display on ValueType in autocomplete.
- lsp: find_navi_toml_pathbuf and all `*.nv` thereunder for cache.
- lsp: look for immediate parent dir in create_compile_options.
- lsp: find cache's member info to do when cfg debug assertions
- lsp: auto complete global vars of a Module.
- lsp: check the client’s capabilities and returns either a simple code action capability or a detailed set of supported code action kinds.
- lsp: allow retrying in dispatch.
- lsp: skeleton of handle_code_action, handle_code_action_resolve.
- lsp: search cache_stdlib, cache_userlib to generate fix advice.
## [v0.10.0](https://github.com/navi-language/navi/releases/tag/v0.10.0)
### Language
- Added `expect`, `unwrap`, `unwrap_or`, `or`, `or_else`, and, `and_then`, `is_nil`, `map`, `map_or`, `ok_or`, `inspect`, `flatten` methods to optional value.
- Added `type alias` statement to define a type alias, and `type` statement to define a newtype.
- Improved `do/catch` to better handle error.
- Added to support call method in union type.
- Added support `tag` attribute for newtype serde.
- Added to support overridden the default imported names in code scope. e.g.: `Error`, you can have your own `struct Error` to override the default `Error`.
### Stdlib
- Removed `std.io.Buffer`, instead with new `std.io.Bytes`.
- Moved module under `lang` to `std`, and default import `string`, `channel`, `Any`, `Decimal`, removed `lang` module.
- Improved to default import `print` and `println` method from `std`, now we can call it directly without `use`.
- Added to support log format (full, json, pretty, compact).
- Renamed `URLEncodedForm` to `UrlEncodedForm` in `std.net.http`.
- Added `File.seek` and `File.rewind` method to `std.fs`.
- Added to support `flag` and `mode` options for `fs.open` and `File.open` method.
- Added `fs.copy`, `fs.copy_dir`, `fs.rename`, `fs.hard_link`, `fs.symlink`, `fs.unlink` method.
- Improved `assert_throws`, the secondary argument support with a closure to write custom assert.
### Pkg
- Added `csv` package to support read and write CSV file.
### Navi Stream
- Add `draw` function.
### Tools
- doc: Added more details doc for array methods.
- doc: Added to support navi-doc to generate method docs in `lang.optional`, `lang.int` ...
- doc: impl Display for Enum, Struct, Interface, and Module.
- doc: show "instance" for nvs object value types.
- fmt: Updated to indent for switch and case with different levels.
- lsp: Added `navi_stream` language match support for Zed editor.
- lsp: Added to support goto definition for Navi to Navi Stream source.
- lsp: Added to support show hover info for struct fields.
- lsp: Improved find symbol of TypePathNode::Normal.
- lsp: determine `language_id` by file extension.
- lsp: find ImportedModule in current module file symbols first.
- lsp: fix bug of there being always an error message left (nvs).
- lsp: generate diagnostics for Navi Stream.
- lsp: optimize inlay hint padding and improve hover info for Navi Stream.
## [v0.9.6](https://github.com/navi-language/navi/releases/tag/v0.9.6)
### Language
- Improved the `navi run` and `navi test` commands for searching the `navi.toml` within a subproject.
- Added support for finding `navi.toml` to locate the workspace path.
- Added the `navi new` command to create a new project.
- Removed the `--all-dir` option from `navi test`, as it is no longer necessary.
- Improved the `array` type to include more methods: `map`, `filter`, `filter_map`, `concat`, `sum`, `max`, `min`, `position_max`, `position_min`, `max_by`, `position_max_by`, `min_by`, `position_min_by`, `product`, `index_by`, `contains_by`, `clone`.
- Improved the `map` type by adding `clone` methods.
- Updated the `spawn` behavior to execute immediately.
- Fixed support to assign `closure` to `closure?`.
- Fixed a bug where the interface default method's first argument must be `self` error on release.
- Improved internal conversion from char to string.
### Stdlib
- Added `std.log`.
- Added `std.io.Write` as Logger output support and added the `prefix` method.
- Fixed serialization to support union types.
- Updated the `to_string` methods of JSON, YAML, and TOML to support `any?` and added more tests.
- Fixed serialization bugs for some complex cases.
### Tools
- We have released the [Zed extension](https://github.com/navi-language/zed-navi) for Zed with LSP support. Now code formatting, code completion, hover, go-to-definition, find references, rename, and more features are available in Zed.
- Open your Zed and go to `Extensions` to search for `navi` and install it.
- Currently, only available for Zed Preview version.
- doc: Added support to generate `enum`, `interface`, `type` for the `navi doc` command.
- doc: Fixed `impl` `enum` `navi doc` generation and included the source code signature by using `navi-fmt` code generation.
- lsp: Fixed LSP absolute path in Zed and other compatibility fixes for Zed.
- lsp: Enhanced LSP to support showing hover info on expr nodes.
- lsp: Fixed LSP inlay hints left padding.
- fmt: Fixed to ensure that the semicolon comes before for long lines.
### Breaking Changes
- Removed the `--all-dir` option from `navi test`, as it is no longer needed.
- Renamed `error` interface to `Error`.
- Removed `execute_many` from the `sql` package because it was deprecated in sqlx.
## [v0.9.5](https://github.com/navi-language/navi/releases/tag/v0.9.5)
### Core
- Add `decimal` as builtin type.
- Fix object pool memory leak.
### Stdlib
- Improve stdlib throws errors, now all errors has it's own error type.
- Add `std.time.Instant`.
- Fix std.time.DateTime, `iso8601` to use **ISO 8601** format, `to_string` to use **RFC 3339** format.
- Add `std.time.Duration` and `std.time.DateTime`, `decimal` to support serialize and deserialize.
- Fix operator (`>`, `>=`, `<`, `<=`) support for `std.time.Duration`.
- Fix `regex.Captures` gc mark leak.
### Pkg
- Add `acquire_timeout` option for `sql.Connection.connect`, default is 30s.
- Add `extra-engine` support to sql.
> **Extra Engine** backend is a experimental feature, it can be used to execute sql with different engine (CSV, Aliyun OSS, AWS S3, MySQL, PostgreSQL, etc).
- Add `close` method to `sql.Rows` to close the rows.
- Add `longport` SDK basic feature to support [LongPort OpenAPI](https://open.longbridgeapp.com).
### Tools
- Improve Navi LSP performance, and improve auto-completion details.
- Improve [zed-navi](https://github.com/navi-language/zed-navi) syntax highlight [v0.0.4](https://github.com/navi-language/zed-navi/releases/tag/v0.0.4)
- Improve [vscode-navi](https://marketplace.visualstudio.com/items?itemName=huacnlee.navi) to support decimal.
## [v0.9.4](https://github.com/navi-language/navi/releases/tag/v0.9.4)
### Language
- Add to support arbitrary parameters.
```rs,no_run
fn foo(a: int, b: int, items: ..string) {
}
```
- Add top support iterate for channel types.
```rs,no_run
use std.io;
let ch = channel::();
spawn {
defer {
ch.close();
}
for i in 0..10 {
ch.send(i);
}
}
for n in ch {
io.println(n);
}
```
- Add to support `impl for`.
```rs
interface Foo {
fn foo(self);
}
struct User {}
impl for User {
fn foo(self) {
io.println("foo");
}
}
```
- Add support lazy initialization.
```rs
let s: string;
s = "hello";
```
- Add `char` type.
```rs
let c: char = 'a';
```
- Add to support `while let`.
```rs
while (let v = ch.recv()) {
io.println(v);
}
```
- Now can iterate over a string.
```rs
let s = "hello";
for c in s {
io.println(c);
}
```
- Improve string performance.
- Improve string interpolation for support `${x:?}` to print debug format.
- Improve implicit conversion to support.
```rs
// Option type
let a: int? = 10;
assert_eq a!, 10;
let a: int??? = 10;
let b: int? = a;
assert_eq b!, 10;
let a: int? = 10;
let b: int??? = a;
assert_eq b!!!, 10;
// Union type
let a: int | string = "abc";
assert_eq a.(string), "abc";
// Interface
let a: ToString = 10;
assert_eq a.to_string(), "10";
```
- Fix the `break` in `do, catch` block can't break the outside `loop` bug.
### Stdlib
- Add `time.sleep` method.
```rs
use std.time;
// sleep 1 second
time.sleep(1);
```
- Improve `std.io.print` to support arbitrary argument.
```rs
use std.io;
io.println("hello", "world");
```
- Update http Headers `apppend`, `set` method, not allows nil value.
- Update http `set_basic_auth` the `username` not allows nil.
### Pkg
The `pkg` is used to manage packages that many split out of Navi in the future.
- Add [sql](https://navi-lang.org/stdlib/sql) module to support SQlite, MySQL, PostgreSQL, etc.
```rs
use sql.Connection;
struct User {
id: int,
name: string,
}
const conn = try! Connection.connect("sqlite::memory:");
try! conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
try! conn.execute("INSERT INTO users (name) VALUES (?)", "Jason Lee");
let users = try! conn.query("SELECT * FROM users").scan::();
```
- Add [markdown](https://navi-lang.org/stdlib/markdown) module to support markdown render to HTML.
```rs
use markdown;
let html = markdown.to_html("# Hello, World!");
```
### Tools
- Fix Navi LSP to better autocomplete and documentations support.
- Add cache support in Navi LSP for better performance.
- Add `format` support in Navi LSP.
- fmt: insert a new line after std uses.
- fmt: sort outer-layer use { ... } statements
- New [tree-sitter-navi](https://github.com/navi-language/tree-sitter-navi), [tree-sitter-navi-stream](https://github.com/navi-language/tree-sitter-navi-stream) project for tree-sitter support.
- Add [Zed extension](https://github.com/navi-language/zed-navi) support, current only syntax highlight support.
## [v0.9.3](https://github.com/navi-language/navi/releases/tag/v0.9.3)
### Language
- Add generic function supports.
- Add the `enum` syntax.
```rs
enum Color {
Red,
Green,
Blue,
}
enum StatusCode {
Ok = 200,
BadRequest = 400,
NotFound = 404,
}
```
- Add the `const` syntax to define a constant.
```rs
const PI = 3.1415926;
```
- Add the `pub` keyword to define a public `fn`, `struct`, `struct` field, `const`, `enum` ..., now only the `pub` members can be visited from other modules.
```rs
pub const PI = 3.1415926;
pub fn add(a: int, b: int): int {
return a + b;
}
pub struct Loc {
pub line: int,
pub col: int,
}
```
- Improve compile error message, and write test case for it.
- Allows to visit the `const` from other modules.
- Add `` r\`raw string\` `` syntax to better write Regex rules.
```rs
use std.regex.Regex;
// Before
let re = Regex.new("\\d+");
// After
let re = Regex.new(r`\d+`);
```
- Improvement performance for internal calls, string, Object Ref.
- Add to support use `_` in a number literal.
```rs
let n = 1_234_567.89_012;
```
- Add interface inheritance support, and support default implementation.
```rs
interface A {
fn a(): int;
}
interface B: A {
fn b(): int {
return 2;
}
}
```
- Improve assignment detect, now array, map can avoid declaring types, if the left side is a known type.
```rs
fn foo(items: [string]) {}
fn bar(items: ) {}
// Before
foo([string] { "a", "b" });
bar( { "a": 1, "b": 2 });
// After
foo({ "a", "b" });
bar({ "a": 1, "b": 2 });
```
- Improve **Struct Field** and **Kw Arguments** assignment, if the variable name is the same as the field name, we can use a short syntax.
```rs
struct User {
name: string,
city: string,
}
impl User {
fn new(name: string = "", city: string = ""): User {
return User {
name: name,
city: city,
}
}
}
let name = "foo";
let city = "bar";
// Before
let user = User { name: name, city: city };
let user = User.new(name: name, city: city);
// After
let user = User { name, city };
let user = User.new(name:, city:);
```
- Add Union Type support.
```rs
fn foo(n: int | string) {
switch (let n = n.(type)) {
case int {
println("int: {}", n);
}
case string {
println("string: {}", n);
}
}
}
// Also can return a union type
fn bar(): int | string {
return 1;
}
```
### Stdlib
- Rewrite stdlib by Navi wrapper (internal is Rust Native), for better interface, and error support.
- Add `std.io.{Read, Write, Close}` interface.
- Rewrite stdlib by using interface `io.Read`, `io.Write` instead of `[byte]` for support stream, mostly for `std.fs` and `std.net.http`.
```rs
use std.net.http;
use std.fs;
let res = try http.get("https://navi-lang.org");
// Before body is a `[byte]`
let body = res.body();
// After
// Body is a std.io.Read interface
/// https://navi-lang.org/stdlib/std.net.http#Response.body
let body = res.body.read_to_string();
```
- Add `std.net.http.Client` to create an HTTP client.
- Add `std.io.Cursor` for writing a `[byte]` or `string` to support `std.io.Read` interface.
- Add `json.from_reader` to support parse JSON from a `std.io.Read` in stream.
- Fix `sort` array incorrect order bug in some cases.
- Add `std.xml` module to support parsing XML.
- Add `std.toml` module to support parsing TOML.
- Add `std.io.StringBuffer` for mutable string like `std.io.Buffer`.
### Tools
- lsp: Add Go to define support for LSP, and support to visit Navi stdlib source code.
- lsp: Add Hover document for LSP.
- lsp: Add support newline to keep `///` comment in next line.
- lsp: Add support a bit of Auto Complete support, still need to improve.
- fmt: `use` statement will be sorted by alphabet, and keep `self` in the first.
- test: Add `navi test --all-dirs` to test all sub-dir, and default test current dir.
- test: Improve `assert` results and use `""` to wrap them for a better read.
- build: We use Navi to write our internal CI build and publish script now.
## [v0.9.1](https://github.com/navi-language/navi/releases/tag/v0.9.1)
### Language
- Add to support error handling, see [Error](https://navi-lang.org/learn#error) doc.
```rs
fn main() throws {
let file = try fs.open("file.txt");
// ...
}
```
- Add **Static Method** support, and rewrite all stdlib.
```rs
struct User {}
impl User {
fn new() -> User {
// ...
}
}
```
- The first argument on the **Instance Method** now must have a `self` parameter.
```rs
struct User {}
impl User {
// Before
fn to_string() {
// ...
}
// After
fn to_string(self) {
// ...
}
}
```
- Add new serialization support for `json`, `yaml` module.
```rs
use std.json;
struct User {
name: string
##[serde(rename = "_age")]
age: int
}
let user = json.parse::(`{"name":"Navi","_age":1}`);
assert_eq user.name, "Navi";
let user = User { name: "Navi", age: 1 };
assert_eq json.to_string(user), `{"name":"Navi","_age":1}`;
```
- Add also support to deserialize to the `any` type.
```rs
let a = json.parse::("1");
assert_eq a.(int), 1;
let a = json.parse::(`"hello"`);
assert_eq a.(string), "hello";
```
- Add **Raw String** syntax: `` r`this is string` `` for better use for the regular expression.
```rs
use std.regex.{Regex};
// Before
let re = Regex.new("[\\w\\d]");
// After
let re = Regex.new(r`[\w\d]`);
```
- Improve the `use` syntax to support `as` and import multiple items.
```rs
// Before
use std.fs;
use std.fs.File;
// After
use std.fs.{self, File as BaseFile, write};
```
- Add `type` keyword to define [Type Alias](https://navi-lang.org/learn#type-alias).
```rs
type Key = string;
type Value = int;
type MyInfo = ;
let info: MyInfo = {
"foo": 1,
"bar": 2,
};
```
- Improve syntax to assign an array, or map without type annotation, if the left side has a known type.
```rs
let a: [int] = {1, 2, 3};
let b: = { "foo": 1, "bar": 2 };
struct Info {
headers:
}
let info: Info = {
headers: { "Content-Type": "application/json" }
};
```
### Stdlib
- Rewrite all stdlib to use **Static Method**, **Error Handling**, and the new **Serialization** support.
- Removed `to_json`, `to_yaml` method, use `json.to_string`, `yaml.to_stirng` instead.
- Add `File.open`, `File.create` and `fs.write`, `fs.write_string`.
- Add `replace_with`, `replace_all_with`, `captures` to `regex.Regex`.
- Rename `match`, `match_all` to `find` and `find_all` for `regex.Regex` to avoid keywords.
- Add `utc`, `to_offset` method to `time.DateTime`.
- Add `step` method to `Range` type.
- Add `bytes` method to Buffer.
- Add `set_file` method to `multipart.Form` to support file upload, see [Multipart](https://navi-lang.org/stdlib/std_net_http_multipart#Form#set_file) doc.
- Rename `to_int`, `to_float` into `parse_int`, `parse_float` for string.
### Tools
- Publish [Navi Learn](https://navi-lang.org/learn), [Navi Stdlib](https://navi-lang.org/stdlib) docs on the website.
- And now use Navi instead of TypeScript to write [scripts](https://github.com/navi-language/website/tree/main/script) for website docs generated.
- All stdlib docs have been generated by `navi doc --stdlib` command.
- Add [Navi Stream](https://navi-lang.org/navi-stream/) doc.
- Add **Outline** display support for LSP and VS Code extension.
- Add **Inlay Hints** support for LSP and VS Code extension.
- Add new syntax support for `navi fmt`.
- Add `navi doc` command to generate documentation for Navi files.
- Add `navi doc --stdlib` command to generate documentation for Navi's standard library.
- Add `navi test --doc` command to test the Markdown code blocks in Navi files.
## [v0.9.0](https://github.com/navi-language/navi/releases/tag/v0.9.0)
### Language
- Add `interface` support.
- Add `navi fmt` to format code, and install the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=huacnlee.navi).
- Add `loop` statement to loop forever.
- Add getter, and setter to built-in type.
- Add dotenv support, now will auto load `.env` file in the current directory as environment variables.
- Add `if let` statement.
- Add `main` function, now `navi run` will run the `main` function in `main.nv` file in default.
- Add [httpbin](https://httpbin.org/) feature into `testharness` to speedup HTTP test.
### Stdlib
- Add `resize`, `truncate`, `split_off`, `chunks` method to array.
- Add `weekday` to `std.time`.
- Add `std.process` with `exit`, `abort`, `pid`, `exec`, `run`, `args` function.
- Add `std.json`, `std.yaml`, `std.querystring` modules for JSON, YAML, QueryString parse, and stringify.
- Add `std.value` module to common value type.
- Add `basic_auth` method to HTTP `Request`.
- Add `chunk` method to HTTP `Response`.
- Add `multipart`, `form`, `timeout` to `http.Request`.
- Add `read`, `read_string` to `fs.File`, and `read`, `read_string` to `fs` module.
- Add `std.io.stdin`, `std.io.stdout`, `std.io.stderr`.
- Add `join` to `std.url`.
- Add `std.env.vars` to get all vars.
### Breaking Changes
- Rewrite string interpolation from `{1 + 2}` to `${1 + 2}` like JavaScript.
- Kw Arguments now use `:` instead of `=`, e.g.: `fn(a: 1, b: 2)`, no longer allowed to be passed positionally.
- Most struct get method in stdlib now is getter, e.g.: `host` instead of `host()` for `url.URL`.
- Rename `fs.read_string` to `fs.read_to_string`.
- Rename `io.from_bytes` to `io.new_bytes_from_array`.
- Move `cwd`, `chdir` from `std.path` to `std.env`.
### Examples
#### Interface
Like Go, you can define an interface in the following way:
```rs
interface Readable {
fn read(): string
}
fn read(r: Readable): string {
return r.read()
}
struct File {
path: string
}
impl File {
fn read(): string {
return "read file"
}
}
struct Body {
content: string
}
impl Body {
fn read(): string {
return "read body"
}
}
fn main() {
file := File{path: "file"}
body := Body{content: "body"}
println(read(file))
println(read(body))
}
```
#### If let
```rs
use std.io;
fn main() {
let name: string? = "Navi";
if let name = name {
io.println("name is ${name}")
} else {
io.println("name is nil");
}
}
```
---
---
# Upload a file
HTTP upload file usually use the `multipart/form-data` content type or send the file as a binary data.
- Send a file as a multipart form data
- Send a file as a binary data
## Send a file as multipart form data
```nv,no_run
use std.{fs.File, net.http.client.{HttpClient, Multipart, Request}};
use std.net.http.OK;
fn main() throws {
let f = try File.open("main.nv");
let client = HttpClient.new();
let multipart = Multipart.new();
multipart.append(f, name: "file");
let req = try Request.post("https://httpbin.org/post").set_multipart(multipart);
let res = try client.request(req);
if (res.status() != OK) {
println("Failed to upload file", try res.text());
return;
}
println(try res.text());
}
```
After run `navi run main.nv` will output:
```json
{
"args": {},
"data": "",
"files": {},
"form": {
"file": "use std.{fs, net.http.{client.{HttpClient, Multipart, Request}, OK}};\n\nfn main() throws {\n let f = try fs.open(\"main.nv\");\n\n let client = HttpClient.new();\n let multipart = Multipart.new();\n multipart.append(f, name: \"file\");\n\n let req = try Request.post(\"https://httpbin.org/post\").set_multipart(multipart);\n let res = try client.request(req);\n if (res.status() != OK) {\n println(\"Failed to upload file\", try res.text());\n return;\n }\n\n println(try res.text());\n}\n"
},
"headers": {
"Content-Type": "multipart/form-data; boundary=12e2cc00691db990-e67f0a357c8ef09c-b5c1423f5cda1185-5de81a96447ef53b",
"Host": "httpbin.org",
"Transfer-Encoding": "chunked",
"X-Amzn-Trace-Id": "Root=1-66f4b2c6-3f33559f6312c84e0e7d350a"
},
"json": null,
"origin": "8.223.23.31",
"url": "https://httpbin.org/post"
}
```
In this case, we open the file using the [`fs.open`](/stdlib/std.fs#method.open) function and pass the file object to the `Multipart.append` method to append the file to the multipart form data. Then we set the multipart form data to the request using the `Request.set_multipart` method.
## Send a file as a binary data
Sometimes, the HTTP server may only accept the file as a binary data, you can use the `File` type to read the file and send it as a binary data.
```nv,no_run
use std.{fs.File, net.http.client.{HttpClient, Request}};
use std.net.http.OK;
fn main() throws {
let f = try File.open("main.nv");
let client = HttpClient.new();
let req = try Request.post("https://httpbin.org/post").set_body(f);
let res = try client.request(req);
if (res.status() != OK) {
println("Failed to upload file", try res.text());
return;
}
println(try res.text());
}
```
After `navi run` above code, the output will be:
```json
{
"args": {},
"data": "use std.{fs, net.http.{client.{HttpClient, Request}, OK}};\n\nfn main() throws {\n let f = try fs.open(\"main.nv\");\n\n let client = HttpClient.new();\n let req = try Request.post(\"https://httpbin.org/post\").set_body(f);\n let res = try client.request(req);\n if (res.status() != OK) {\n println(\"Failed to upload file\", try res.text());\n return;\n }\n\n println(try res.text());\n}\n",
"files": {},
"form": {},
"headers": {
"Host": "httpbin.org",
"Transfer-Encoding": "chunked",
"X-Amzn-Trace-Id": "Root=1-66f4b331-00ee4e0327d0088e3ac596a2"
},
"json": null,
"origin": "8.223.23.31",
"url": "https://httpbin.org/post"
}
```
In this case, we open the file using the [`fs.open`](/stdlib/std.fs#method.open) function and pass the file object to the `Request.set_body` method to set the file as the request body.
---
# Use
`use` statement is used to import functions from standard library “package” into the current scope, so we can use the functions directly.
::: info
Unlike Navi's package, in Navi Stream, all of the stdlib has been default imported, so you can use them directly with out `use`.
:::
## With `use`
When using the `use` statement to import a namespace, we can directly use the `ma` function, as well as all functions in [ta]:
```nvs
use ta;
let avg = ma(value, 10);
```
You also can import multiple packages at once:
```nvs
use ta, quote, math;
let avg = abs(-1);
// avg = 1
```
## Without `use`
By default, we can use `.` to call functions in the standard library without using `use`. For example, the following statement calls the `ma` function in the [ta] namespace.
```nvs
let avg = ta.ma(value, 10);
```
[ta]: ../../stdlib/ta.md
---
# Use
`use` 语句用于将标准库“module”中的函数导入到当前作用域中,这样我们就可以直接使用这些函数。
::: info
与 Navi 的 module 略有不同,在 Navi Stream 中,所有的 stdlib 都已经默认导入,所以你可以直接使用它们,无需 `use`。
而使用 `use` 是会将 module 内部的函数直接导入到当前作用域,这样你就可以直接使用这些函数,而不需要使用 `.` 的形式。
:::
## With `use`
当你使用 `use` 语句导入一个 namespace 时,我们可以直接使用 `ma` 函数,以及 [ta] 中的所有函数:
```nvs
use ta;
let avg = ma(value, 10);
```
你也可以一次导入多个包:
```nvs
use ta, quote, math;
let avg = abs(-1);
// avg = 1
```
## Without `use`
我们也可以直接使用 `.` 的形式来调用标准库中的函数,而不使用 `use`。例如,下面的语句调用了 [ta] 命名空间中的 `ma` 函数。
```nvs
let avg = ta.ma(value, 10);
```
[ta]: ../../stdlib/ta.md
---
# Using TcpConnection
Each accepted connection gives a [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection) instance, and you can then spawn a new Navi coroutine to handle the connection.
This guide provides an overview of the [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection) type, focusing on creating a connection, reading from it, writing to it, and shutting it down. By following this guide, you will be able to understand and implement TCP communication in their applications.
## What is TcpConnection?
A [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection) represents a TCP connection between a local and a remote socket.
Reading and writing to a [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection) is typically done using the convenience methods found on the `Read` and `Write` traits. The `write_all()` method is defined on the `Write` trait.
## Creating a TcpConnection
A [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection) can be created by accepting a connection from a listener. An example is shown below.
```nv,no_run
use std.net.TcpListener;
fn main() throws {
let listener = try TcpListener.bind("127.0.0.1:3000");
loop {
let conn = try listener.accept();
spawn {
// Handling the connection
}
}
}
```
## Read
The `read()` method pulls some bytes from the [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection) into the specified buffer, returning the number of bytes read. An example is shown below.
```nv,ignore
let buf = Bytes.new(len: 1024);
let n = try! conn.read(buf);
io.println(`read ${n} bytes`);
```
In the above example, a buffer of `1024` bytes is created to hold the data read from the connection. The `read()` method reads data from the connection into the buffer. The number of bytes read is returned. The number of bytes read is printed.
## Write
The `write()` method writes a buffer into the [`std.net.TcpConnection`](/stdlib/std.net.TcpConnection), returning the number of bytes written. The `flush()` method ensures that all intermediately buffered contents reach their destination. An example is shown below.
```nv,ignore
let data = "hello world".bytes();
let n = try! conn.write(data);
try! conn.flush();
io.println(`wrote ${n} bytes`);
```
In the above example, a buffer containing the string `"Hello, world!"` is created. The [`write`](/stdlib/std.io.Write#method.write) method writes the data to the connection. The number of bytes written is returned. The [`flush`](/stdlib/std.io.Write#method.flush) method ensures that all buffered data is sent. The number of bytes written is printed.
## The Remote Address
For each accepted connection, you can obtain the remote address of the client using the connection's [`remote_addr`](/stdlib/std.net.Connection#method.remote_addr) method.
## Close The Connection
To close the connection in the write direction, you can call the [`close`](/stdlib/std.io.Close#method.close) method. This will cause the other peer to receive a read of length `0`, indicating that no more data will be sent. This only closes the connection in one direction. An example is shown below.
```nv,ignore
try conn.close();
```
---
# Using TcpListener
This guide introduces how to use `std.net.TcpListener` to create a basic TCP server in Navi. The guide teaches how to bind to an address, accept incoming connections, and handle data reading and writing, etc. By understanding and using the TcpListener library, you can write efficient and robust TCP application servers using Navi.
## What is TcpListener?
A TcpListener is a TCP socket server that listens for incoming connections. It allows you to accept new connections and handle them in your application.
You can accept a new connection by using the `accept()` method.
## Creating a TcpListener
```nv,no_run
use std.net.TcpListener;
let listener = try! TcpListener.bind("127.0.0.1:3000");
```
This creates a new TcpListener, which will be bound to the specified address. The returned listener is ready for accepting connections.
Binding with a port number of `0` will request that the OS assigns a port to this listener. The port allocated can be queried via the `local_addr()` method explained below.
## The Listening Address
The method `local_addr()` returns the local address that the listener is bound to. This can be useful, for example, when binding to port 0 to figure out which port was actually bound.
```nv,ignore
io.println(`listening on ${try! listener.local_addr()}`);
```
## Accepting Connections
```nv,ignore
let stream = try! listener.accept();
```
The call to `accept()` method will yield once a new TCP connection is established. When established, the corresponding `TcpStream` and the remote peer's address will be returned. See the TcpStream guide for details.
---
---
order: 1
---
# Variables
In Navi Stream we have 3 keywords to store value: `let`, `var` and `varip`.
The syntax of variable declarations is:
```
[] :[] =
```
where:
- `declaration_mode` - is the variable mode, we can use `let`, `var`, `varip` 3 kinds.
- `type` - used to declare the variable type, such as `number`, `string` (optional parameter).
- `identifier` - variable name.
- `expression` - the value of the variable, can be any expression.
## let
`let` for define a mutable variable. Like `let` in JavaScript.
::: info
We don't have mutable and inmutable types in Navi Stream, so you can change the value of a variable at any time.
:::
```nvs
let b = 8;
let a = 1 + b;
// Now `a` value is 9
a += 2;
// Now `a` value is 11
```
You can assignment a new value to a variable at any time.
```nvs
let a = 1;
a = 2;
a = 3;
```
## var
`var` use for stream processing, it's like `let` but it's reset to the initial value at the end of each period.
::: tip
`var` is a periodic `let`, its value is only fixed at the end of each period, and other times it is reset to the value at the beginning of the period.
:::
The following example shows how to use `var` to calculate the current period:
```nvs
var bar = 0;
bar += 1;
```
After run the code, we can get the following result:
| idx | bar |
| --- | --- |
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 1 |
| 6 | 1 |
| 7 | 1 |
| 8 | 1 |
| 9 | 1 |
| 10 | 2 |
The result is consistent with the 5m periodic rule, you can see that the value of `var` variable is only determined at the end of the last 5m.
The following diagram shows the change of the `var` variable:
```mermaid
gantt
title Trade (5m)
dateFormat HH:mm
section Periods
period 1 :m1, 10:00, 5m
period 2 :m1, 10:05, 5m
period 3 :m1, 10:10, 1m
section Navi Stream var
var bar = 0 :n1, 10:00, 1m
bar is 1 :n1, 10:05, 1m
bar is 1 :n1, 10:06, 1m
bar is 1 :n1, 10:09, 1m
bar is 2 :n1, 10:10, 1m
```
## varip
`varip` use for stream processing, ensure that the value of each period is independent.
### Use case
| idx | time | price | volume |
| --- | ----- | ------ | ------ |
| 1 | 10:00 | 100.25 | 300 |
| 2 | 10:01 | 100.50 | 200 |
| 3 | 10:02 | 100.75 | 100 |
| 4 | 10:03 | 101.00 | 300 |
| 5 | 10:04 | 101.25 | 200 |
| 6 | 10:05 | 101.50 | 100 |
| 7 | 10:06 | 101.75 | 300 |
| 8 | 10:07 | 102.00 | 200 |
| 9 | 10:08 | 102.25 | 100 |
| 10 | 10:09 | 102.50 | 300 |
| 11 | 10:10 | 102.75 | 200 |
Now, we use `varip` to calculate the total amount of each period (5m):
```nvs
varip total_amount = 0;
total_amount += trade.volume;
```
If we calculate it, we can get:
| idx | total_amount |
| --- | ------------ |
| 1 | 300 |
| 2 | 500 |
| 3 | 600 |
| 4 | 900 |
| 5 | 1100 |
| 6 | 1200 |
| 7 | 1500 |
| 8 | 1700 |
| 9 | 1800 |
| 10 | 2100 |
| 11 | 2300 |
### barstate.is_confirmed
::: warning
Navi Stream's calculation cycle is a little special, the last data in each cycle (period) **will be calculated twice**, the last data will be calculated once in **confirmed** mode.
When in the last data of the period, `barstate.is_confirmed` is `true`, so we need to judge it to avoid `total_amount` being calculated twice.
:::
We expected to output data only at the end of each period, so we can write like this:
```nvs
varip total_amount = 0;
if (barstate.is_confirmed) {
alert(`total_amount: {total_amount}`);
} else {
total_amount += trade.volume;
}
```
Then Navi Stream well send `alert` like this:
| idx | total_amount |
| --- | ------------------- |
| 5 | "total_amount 1100" |
| 11 | "total_amount 2300" |
---
# While
`while` statement is used to repeat a block of statements while a condition is true.
This code show up how many periods are needed to fully turn over the capital.
```nvs
let i = 1;
let total_vol = 0;
while (total_vol <= capital) {
total_vol = total_vol + vol[i];
i = i + 1;
}
```
:::warning
In a `while` block, you can't use stateful functions, such as `ma`, `sum`.
:::
---
# While
`while` 语句用于在条件为 true 时重复执行一组语句。
下面的示例展示了 `while` 语句的基本语法:
```nvs
let i = 1;
let total_vol = 0;
while (total_vol <= capital) {
total_vol = total_vol + vol[i];
i = i + 1;
}
```
:::warning
在 `while` 块中,不能使用状态函数,例如 `ma`、`sum`。
:::
---
# Write a string to a file
The [fs.write](/stdlib/std.fs#write) function is used to write a string to a file. The first argument is a string of the file path, and the second argument is a `string` or [std.io.Bytes](/stdlib/std.io#std.io.Bytes) of the content.
If the file does not exist, it will create a new file. If the file exists, it will **overwrite** the file content.
```nv, no_run
use std.fs;
fn main() throws {
try fs.write_string("output.txt", "Hello, world!\n");
}
```
Then the `output.txt` file will contain the following content:
```txt
Hello, world!
```
## Alternative
You can use [fs.open](/stdlib/std.fs#open) or [File.open](/stdlib/std.fs#File.open) method to open a file in write mode and get a [std.fs.File](/stdlib/std.fs#std.fs.File) instance, and use the `write` method to write a bytes to the file, use `write_string` method to write a string to the file.
In this case, you must special the `flag` argument to open the file in write mode (the default flag is `fs.READ`, that means read-only mode). So you must use `fs.WRITE` flag to open the file in write mode. And with `fs.CREATE` flag, it will create a new file if the file does not exist.
```nv, no_run
use std.fs.{self, File};
fn main() throws {
let f = try File.open("output.txt", flag: fs.WRITE | fs.CREATE);
defer {
try! f.close();
}
try f.write_string("Hello, world!\n");
}
```
---
---
order: 1
---
# 变量
在 Navi Stream 中我们有 3 种关键字来存储值:`let`、`var` 和 `varip`。
变量声明的语法是:
```
[] :[] =
```
其中:
- `declaration_mode` - 是变量模式,我们可以使用 `let`、`var`、`varip` 3 种。
- `type` - 用于声明变量类型,如 `number`、`string`(可选参数)。
- `identifier` - 变量名。
- `expression` - 变量的值,可以是任何表达式。
## let
`let` 用于定义一个可变变量,类似于 JavaScript 中的 `let`。
::: info
我们在 Navi Stream 中没有可变和不可变类型,所以你可以随时更改变量的值。
:::
```nvs
let b = 8;
let a = 1 + b;
// Now `a` value is 9
a += 2;
// Now `a` value is 11
```
你可以随时为变量赋新值。
```nvs
let a = 1;
a = 2;
a = 3;
```
## var
`var` 用于流处理,它类似于 `let`,但是在每个周期结束时重置为初始值。
::: tip
`var` 是一个周期性的 `let`,它的值只在每个周期结束时才固定,其他时间它会重置为周期开始时的值。
:::
下面的示例展示了如何使用 `var` 来计算当前周期:
```nvs
var bar = 0;
bar += 1;
```
当我们运行这段代码后),我们可以得到以下结果:
| idx | bar |
| --- | --- |
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 1 |
| 6 | 1 |
| 7 | 1 |
| 8 | 1 |
| 9 | 1 |
| 10 | 2 |
上面的结果符合 5m 周期规则,你可以看到 `var` 变量的值只在最后一个 5m 结束时才确定。
下面的流程图展示了 `var` 变量的变化:
```mermaid
gantt
title Trade (5m)
dateFormat HH:mm
section Periods
period 1 :m1, 10:00, 5m
period 2 :m1, 10:05, 5m
period 3 :m1, 10:10, 1m
section Navi Stream var
var bar = 0 :n1, 10:00, 1m
bar is 1 :n1, 10:05, 1m
bar is 1 :n1, 10:06, 1m
bar is 1 :n1, 10:09, 1m
bar is 2 :n1, 10:10, 1m
```
## varip
`varip` 用于流处理,它类似于 `var`,但是在每个周期结束时重置为初始值,并且在每个周期结束时输出一个值。
### Use case
| idx | time | price | volume |
| --- | ----- | ------ | ------ |
| 1 | 10:00 | 100.25 | 300 |
| 2 | 10:01 | 100.50 | 200 |
| 3 | 10:02 | 100.75 | 100 |
| 4 | 10:03 | 101.00 | 300 |
| 5 | 10:04 | 101.25 | 200 |
| 6 | 10:05 | 101.50 | 100 |
| 7 | 10:06 | 101.75 | 300 |
| 8 | 10:07 | 102.00 | 200 |
| 9 | 10:08 | 102.25 | 100 |
| 10 | 10:09 | 102.50 | 300 |
| 11 | 10:10 | 102.75 | 200 |
现在,我们想要计算每个周期(5m)的总量,我们可以使用 `varip` 来计算:
```nvs
varip total_amount = 0;
total_amount += trade.volume;
```
如果我们计算它,我们可以得到:
| idx | total_amount |
| --- | ------------ |
| 1 | 300 |
| 2 | 500 |
| 3 | 600 |
| 4 | 900 |
| 5 | 1100 |
| 6 | 1200 |
| 7 | 1500 |
| 8 | 1700 |
| 9 | 1800 |
| 10 | 2100 |
| 11 | 2300 |
### barstate.is_confirmed
::: warning
Navi Stream 的计算周期有点特殊,每个周期(期间)的最后一个数据**将被计算两次**,最后一个数据将在**确认**模式下计算一次。
在最后一个数据是,`barstate.is_confirmed` 的状态为 `true`,所以我们可以判断它,以避免 `total_amount` 被计算两次。
:::
我们期望只在每个周期结束时输出数据,所以我们可以这样写:
```nvs
varip total_amount = 0;
if (barstate.is_confirmed) {
alert(`total_amount: {total_amount}`);
} else {
total_amount += trade.volume;
}
```
这样一来,Navi Stream 将会发送 `alert`,如下所示:
| idx | total_amount |
| --- | ------------------- |
| 5 | "total_amount 1100" |
| 11 | "total_amount 2300" |
---
---
order: 2
---
# 字符串
我们可以使用双引号 `"` 和反引号 `` ` `` 来创建字符串字面量。
```nvs
let a = "你好世界";
let b: string = `hello world`;
```
## 字符串插值
我们可以使用 `${}` 来将一个表达式插入到字符串中,你必须使用反引号 `` ` `` 来创建一个字符串字面量。
```nvs
let a = 100;
let b = `你好 ${a + 2}`;
// b = "你好 102"
```
## 方法
### to_number
将字符串转换为数字。
```nvs
let a = "100";
a.to_number();
// 100
let b = "3.1415";
b.to_number();
// 3.1415
```
### to_lowercase
将字符串转换为小写。
```nvs
let a = "你好世界";
a.to_lowercase();
// "你好世界"
```
### to_uppercase
将字符串转换为大写。
```nvs
let a = "你好世界";
a.to_uppercase();
// "你好世界"
```
### substring
从字符串中获取一个子字符串。
```nvs
let a = "Hello World";
a.substring(0, 5);
// "Hello"
```
### replace
替换字符串中的所有匹配子字符串。
```nvs
let a = "Hello World";
let b = a.replace("Hello", "Hi");
// b is "Hi World"
a = "Hello World";
b = a.replace("l", "L");
// b is "HeLLo WorLd"
```
### len
返回字符串中的字符数。
```nvs
let a = "你好 Navi Stream 🌈";
a.len();
// 9
```
### contains
检查字符串是否包含子字符串。
```nvs
let a = "Hello World";
a.contains("Hello");
// true
a.contains("hello");
// false
```
### starts_with
检查字符串是否以子字符串开头。
```nvs
let a = "Hello World";
a.starts_with("Hello");
// true
a.starts_with("foo");
// false
```
### ends_with
检查字符串是否以子字符串结尾。
```nvs
let a = "Hello World";
a.ends_with("World");
a.ends_with("foo");
// false
```
### split
分割字符串为一个字符串列表。
```nvs
let a = "Hello World";
let b = a.split(" ");
// b is ["Hello", "World"]
b.len()
// 2
```
### trim
删除字符串两端的空白字符。
```nvs
let a = " Hello World ";
let b = a.trim();
// b is "Hello World"
```
### trim_start
删除字符串开头的空白字符。
```nvs
let a = " Hello World ";
let b = a.trim_start();
// b is "Hello World "
```
### trim_end
删除字符串末尾的空白字符。
```nvs
let a = " Hello World ";
let b = a.trim_end();
// b is " Hello World"
```
---
---
order: -98
---
# 字面量
## bool
`bool` 是一个内置类型,你可以用它来定义一个变量,`true` 和 `false` 是布尔值。
```nvs
let a: bool = true;
let b = false;
```
## number
在 Navi Stream 中,我们使用 `number` 来表示所有的数值,包括整数和浮点数。
```nvs
let a: number = 1;
let a = 3.1415;
```
## string
我们可以使用双引号 `"` 和 `` ` `` 来创建一个字符串字面量。
```nvs
let a = "hello world";
let b: string = `你好世界`;
```
## nil
`nil` 是一个特殊的值,它表示没有任何东西,用来表示一个空值。
```nvs
let a = nil;
```
## color
不同于大多数编程语言,我们在 Navi Stream 中有一个 `color` 类型,用来表示图表绘制的颜色。
它像 CSS 语法,我们可以使用 `#` 来定义一个颜色,然后使用 HEX 颜色或者预定义的颜色名字,如 `red`、`blue`、`green`。
::: info
你可以自由地使用任何颜色,Navi Stream 不会处理颜色,它会直接将颜色输出到图表。
:::
```nvs
let a: color = #ff00ff;
let b = #red;
```
---
# 安装 Navi
如果你使用的是 Linux 或 macOS,你可以通过在终端中运行以下命令来安装 Navi:
```bash
curl -sSL https://navi-lang.org/install | bash
```
> 此脚本也可以用于升级 Navi 到最新版本。
安装脚本将 Navi 安装到 `~/.navi` 目录中。
并将 `~/.navi` 添加到你的 `$PATH` 环境变量中,这样你就可以直接访问 `navi` 命令。
安装成功后,你可以运行 `navi -h` 来检查是否安装成功。
```bash
$ navi -h
```
::: tip
如果找不到 `navi`,你可能需要重新启动终端以重新加载 `$PATH` 环境变量。
或者只需将 `export PATH="$HOME/.navi:$PATH"` 添加到你的 shell 配置文件中,并重新加载。
:::
## 安装特定的 Navi 版本
你可以通过将版本号传递给脚本来安装特定版本。
::: code-group
```bash [Nightly 版本]
curl -sSL https://navi-lang.org/install | bash -s -- nightly
```
```bash [特定版本]
curl -sSL https://navi-lang.org/install | bash -s -- v0.9.0-nightly
```
:::
---
---
order: 0
---
# 布尔值
Bool 是一种表示布尔值的对象类型。
## 创建布尔值
```nvs
let a = true;
let b = false;
```
## 方法
### to_string
将布尔值转换为字符串。
```nvs
let a = true;
a.to_string();
// "true"
```
### to_number
将布尔值转换为数字。
```nvs
let a = true;
a.to_number();
// 1
a = false;
a.to_number();
// 0
```
---
---
order: -99
---
# 数字
在 Navi Stream 中,`number` 类型用于定义整数或浮点数。
::: info
在内部,`number` 实际上并不是一个对象,它是一个原始类型。但我们计划在未来将其更改为一个对象。
:::
```nvs
let a = 1;
let b = 2;
let c = a + b + 3;
// 6
c.to_string();
// "6"
```
## 方法
### to_string
将数字转换为字符串。
```nvs
let a = 1;
a.to_string();
// "1"
```
---
---
order: 3
---
# 数组
数组用于在单个变量中存储多个值。
## 语法
要初始化数组,使用 `[]` 操作符(更像 Go 语言)。
```nvs
let items = [number] { 1, 2, 3 };
items.sum();
// 6
items.len();
// 3
```
## 初始化数组
你也可以定义多种类型的数组。
```nvs
// 数字数组
let number_items = [number] { 1, 2, 3 };
// 字符串数组
let string_items = [string] { "hello", "world" };
// 结构体数组
struct Item {
name: string
}
let struct_items = []Item{
Item { a: 1, b: "hello" },
Item { a: 2, b: "world" },
};
// 颜色数组
let color_items = [color] { #ff0000, #00ff00, #purple };
```
## 方法
### len
获取数组的长度。
```nvs
let items = [number] { 1, 2, 3 };
items.len();
// 3
```
### iter
遍历数组。
```nvs
let items = [number]{ 1, 2, 3 };
items.iter((item) => {
item.to_string();
});
```
### slice
获取数组的切片(与 JavaScript 相同)。
```nvs
let items = [number] { 1, 2, 3, 4, 5 };
items.slice(1, 3);
// [2, 3]
```
### clear
删除数组中的所有项。
```nvs
let items = [number] { 1, 2, 3 };
items.clear();
// 0
```
### reverse
反转数组成员的顺序。
```nvs
let items = [number] { 1, 2, 3 };
items.reverse();
// [3, 2, 1]
let str_items = [string] { "a", "b", "c" };
str_items.reverse();
// ["c", "b", "a"]
```
### push
添加一个元素到数组的末尾。
```nvs
let items = []number{1, 2, 3};
items.push(4);
items;
// [1, 2, 3, 4]
```
### pop
从数组的末尾删除一个元素,并返回该元素。
```nvs
let items = [number]{ 1, 2, 3 };
items.pop();
// 3
items;
// [1, 2]
```
### shift
在数组的开头插入一个元素。
```nvs
let items = [number] { 1, 2, 3 };
items.shift(0);
items;
// [0, 1, 2, 3]
```
### unshift
从数组的开头删除一个元素,并返回该元素。
```nvs
let items = [number]{ 1, 2, 3 };
items.unshift();
// 1
items;
// [2, 3]
```
### remove
删除数组汇总指定位置的元素,并返回该元素。
```nvs
let items = [string] { "a", "b", "c" };
items.remove(1);
// "a"
items;
// ["b", "c"]
```
### get
获取给定索引处的项。
```nvs
let items = [string] { "a", "b", "c" };
items.get(1);
// "b"
```
### set
将指定索引处的项替换为新值。
```nvs
let items = [string] { "a", "b", "c" };
items.set(1, "d");
items;
// ["a", "d", "c"]
```
### iter
创建一个数组的 [Iterator]。
```nvs
let items = [number] { 1, 2, 3 };
let iter = items.iter();
iter.next();
// 1
```
### clone
Clone 一个数组,成为一个新的集合。
```nvs
let items = []number{ 1, 2, 3 };
let items1 = items.clone();
// items1 is [1, 2, 3]
items.clear()
// items is [], items1 still is [1, 2, 3]
```
## Methods for `[number]`
### sum
对数组成员进行 sum 运算。
```nvs
let items = [number] { 1, 2, 3 };
items.sum();
// 6
```
[iterator]: ./iterator
### avg
对数组成员进行 avg 运算。
```nvs
let items = [number] { 1, 2, 3 };
items.avg();
// 2
```
### min
返回数组的最小值。
```nvs
let items = [number] { 1, 2, 3 };
items.min();
// 1
```
### max
返回数组的最大值。
```nvs
let items = [number] { 1, 2, 3 };
items.max();
// 3
```
### sort
对数组进行排序,返回一个新的数组。
```nvs
let items = [number] { 2, 4, 3, 1 };
items.sort();
// items is [1, 2, 3, 4]
```
## Methods for `[string]`
### join
用给定的分隔符连接数组,并返回一个字符串。
```nvs
let items = [string] { "hello", "world" };
let a1 = items.join(" ");
// a1 is "hello world"
let items1 = "hello world".split(" ").join(",");
// items1 is "hello,world"
```
---
---
order: 2
---
# 日期时间
我们可以使用 `time` 包来创建一个 `DateTime` 对象。
## 使用
```nvs
let t = time.parse("2023-04-13 09:45:26")
// t is a DateTime object
t.year
// 2023
t.month
// 4
t.day
// 13
let t1 = time.parse("invalid time")
// t1 is nil
```
## 类方法
### new
创建一个新的 DateTime 对象。
```nvs
let t = time.new(2023, 1, 11, 0, 0, 0);
// t is a DateTime object
export let t_str = t.to_string();
// t_str is "2023-01-11T00:00:00Z"
```
### parse (time)
解析一个字符串为 DateTime 对象,默认时间格式为 [RFC3339]。
如果时间格式无效,将返回 `nil`。
如果字符串内没有包含时区信息,将会默认采用 `UTC` 时区。
```nvs
let t = time.parse("2023-04-13T14:08:33-11:00");
```
或者也兼容支持 `%Y-%m-%d %H:%M:%S` 格式。
```nvs
let t = time.parse("2023-04-13 09:45:26");
```
### parse (time, format)
你可以采用 `parse` 方法传递两个参数,第一个是时间字符串,第二个是时间格式。
```nvs
time.parse("2023-04-13 09:45:26", "%Y-%m-%d %H:%M:%S");
time.parse("2023-04-13 09:45:26", "%Y-%m-%d %H:%M:%S %z");
time.parse("04/13/2023 09:45:26", "%m/%d/%Y %H:%M:%S");
```
更多细节的时间格式,参见 [Time Format]。
## 实例方法
### year
返回时间的年份。
```nvs
let t = time.parse("2023-04-13 09:45:26")
t.year
// 2023
```
### month
返回月份。
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.month;
// 4
```
### day
返回时间的日期。
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.day;
// 13
```
### hour
返回时间的小时。
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.hour;
// 9
```
### minute
返回时间的分钟。
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.minute;
// 45
```
### second
返回时间的秒数。
```nvs
let t = time.parse("2023-04-13 09:45:26");
t.second;
// 26
```
### timestamp
返回时间的 Unix 时间戳(秒)格式的数字。
```nvs
let t = time.parse("2023-04-13 09:45:26 +08:00");
t.timestamp;
// 1681350326
```
### format
基于参数的 [Time Format] 返回时间字符串。
```nvs
let t = time.parse("2023-04-13 09:45:26 +08:00");
t.format("%Y-%m-%d %H:%M:%S %z");
// 2023-04-13 09:45:26 +0800
t.format("%m/%d/%Y %H:%M");
// 04/13/2023 09:45
```
### iso8601
> alias: [to_string](#to_string)
返回时间字符串,格式为 [RFC3339]。
```nvs
let t = time.parse("2023-04-13 09:45:26 +08:00");
t.iso8601;
// 2023-04-13T09:45:26+08:00
```
### to_string
> alias: [iso8601](#iso8601)
返回时间字符串,格式为 [RFC3339]。
## Time Format
| Code | Example | Description |
| ---- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `%a` | `Sun` | Weekday as locale’s abbreviated name. |
| `%A` | `Sunday` | Weekday as locale’s full name. |
| `%w` | `0` | Weekday as a decimal number, where 0 is Sunday and 6 is Saturday. |
| `%d` | `08` | Day of the month as a zero-padded decimal number. |
| `%b` | `Sep` | Month as locale’s abbreviated name. |
| `%B` | `September` | Month as locale’s full name. |
| `%m` | `09` | Month as a zero-padded decimal number. |
| `%y` | `13` | Year without century as a zero-padded decimal number. |
| `%Y` | `2013` | Year with century as a decimal number. |
| `%H` | `07` | Hour (24-hour clock) as a zero-padded decimal number. |
| `%I` | `07` | Hour (12-hour clock) as a zero-padded decimal number. |
| `%p` | `AM` | Locale’s equivalent of either AM or PM. |
| `%M` | `06` | Minute as a zero-padded decimal number. |
| `%S` | `05` | Second as a zero-padded decimal number. |
| `%f` | `000000` | Microsecond as a decimal number, zero-padded on the left. |
| `%z` | `+0000` | UTC offset in the form ±HHMM\[SS\[.ffffff\]\] (empty string if the object is naive). |
| `%Z` | `UTC` | Time zone name (empty string if the object is naive). |
| `%j` | `251` | Day of the year as a zero-padded decimal number. |
| `%U` | `36` | Week number of the year (Sunday as the first day of the week) as a zero padded decimal number. All days in a new year preceding the first Sunday are considered to be in week 0. |
| `%W` | `35` | Week number of the year (Monday as the first day of the week) as a decimal number. All days in a new year preceding the first Monday are considered to be in week 0. |
| `%c` | `Sun Sep 8 07:06:05 2013` | Locale’s appropriate date and time representation. |
| `%x` | `09/08/13` | Locale’s appropriate date representation. |
| `%X` | `07:06:05` | Locale’s appropriate time representation. |
| `%%` | `%` | A literal `%` character. |
[rfc3339]: https://tools.ietf.org/html/rfc3339
[time format]: #time-format
[unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
---
---
order: -99
---
# 标识符
标识符是变量([let])或函数([fn])的名称。它必须以字母或下划线开头,后面可以跟任意数量的字母、数字或下划线。
## 有效的示例
```
var1
_var1
_var_1
_Var1
```
## 无效的示例
```
1var
var 1
var-1
```
## 关键字
你必须避免使用 Navi Stream 的关键字,否则会导致语法错误。
以下是关键字列表(并非全部),请遵循编译器的检查结果。
```
let
var
varip
nil
true
false
for
to
step
while
continue
break
if
else
fn
return
param
meta
export
import
use
switch
case
default
plot
```
[let]: statement/assign.md
[fn]: statement/function.md
---
---
order: 2
---
# 注释
与许多常见的编程语言一样,Navi Stream 使用 `//` 来表示注释。
```nvs
// 这是一行注释
let a = 1; // 这是语句后的注释
```
---
---
order: 6
---
# 结构体
与 Navi 一样,Navi Stream 支持结构体 Struct。
## Struct 定义
```nvs
struct QuoteInfo {
symbol: string,
price: number,
volume: number
}
```
## Struct 初始化
```nvs
let quote_info = QuoteInfo {
symbol: "AAPL",
price: 100.0,
volume: 1000
}
// Mutate struct
quote_info.symbol = "MSFT"
quote_info.price = 200.0
```
---
# 范围
范围是一种特殊的对象,允许你创建一系列的值。它对于创建数字列表或字符列表非常有用。
## 使用
```nvs
let a = 1..5;
// a 包含 [1, 2, 3, 4],不包含 5
let iter = a.iter();
iter.next(); // 1
iter.next(); // 2
iter.next(); // 3
iter.next(); // 4
iter.next(); // nil
```
## 方法
### collect
返回一个包含范围内所有值的[数组]。
```nvs
let a = 1..5;
export let b = a.collect();
// b 是 [1, 2, 3, 4]
```
### iter
返回一个可以用来遍历范围的[迭代器]。
```nvs
let a = 1..5;
let iter = a.iter();
iter.next(); // 1
```
或者你可以使用 `for` 循环来遍历范围。
```nvs
for (let i in 1..5) {
// i 是 1, 2, 3, 4
}
```
### step
返回一个新的 `Range`,其中包含给定步长的范围内的值。
```nvs
let a = 1..5;
export let b = a.step(2).collect();
// b 是 [1, 3]
for (let i in 1..5.step(2)) {
// i 是 1, 3
}
```
---
# 赋值
与大多数编程语言一样,使用 `=` 来给变量赋值。
```nvs
let a = 10 + 20;
// a = 30
a = a + 1;
// a = 31
```
我们也可以使用 `+=`、`-=`、`*=`、`/=` 来简化赋值操作。
```nvs
let a = 10;
a += 1;
// a = 11
a -= 1;
// a = 10
a *= 2;
// a = 20
a /= 2;
// a = 10
```
当你第一次声明一个变量时,你不能再次使用 `let` 来声明同一个变量,编译器会报错。
```nvs
let a = 10;
let a = 20;
// Compile error
```
---
---
order: 0
---
# 运算符
与大多数编程语言一样,Navi Stream 支持基本的算术和逻辑运算符。
Navi Stream 也遵循传统编程语言的优先级,因此您可以根据以前的编程习惯继续使用 Navi Stream 语法。
```nvs
let a = 100 + 2 - 10 * 5 / 2 % 3;
// 101
```
## 运算符
| 运算符 | 描述 |
| ----------------------------- | --------------------- |
| `!a` | 位或逻辑取反 |
| `a + b` | 算术加法 |
| `a += b` | 算术加法并赋值 |
| `-a` | 算术取负 |
| `a - b` | 算术减法 |
| `a -= b` | 算术减法并赋值 |
| `a * b` | 算术乘法 |
| `a *= b` | 算术乘法并赋值 |
| `a / b` | 算术除法 |
| `a /= b` | 算术除法并赋值 |
| `a % b` | 算术取余 |
| `a %= b` | 算术取余并赋值 |
| `a < b` | 小于比较 |
| `a <= b` | 小于或等于比较 |
| `a = 1` | 赋值/等价 |
| `a == b` | 等于比较 |
| `a > b` | 大于比较 |
| `a >= b` | 大于或等于比较 |
| `a != n` | 不等于比较 |
| `a && b` | `与` 逻辑 |
| a || b
| `或` 逻辑 |
| `expr.ident` | 成员访问 |
| `a[n]` | 引用前 `n` 周期的数据 |
## 引用前数据
我们可以使用 `quote.close[n]` 来引用前一个周期的数据。
例如,我们有 K 线(1 分钟)的数据:
| idx | 时间 | 收盘价 |
| --- | ----- | ------ |
| 1 | 10:00 | 10.25 |
| 2 | 10:01 | 10.50 |
| 3 | 10:02 | 10.75 |
| 4 | 10:03 | 11.00 |
| 5 | 10:04 | 11.25 |
如果现在在第 5 周期:
- `quote.close[0]` 是当前数据。
- `quote.close[1]` 是前 1 个周期的数据,值为 `11.00`。
- `quote.close[2]` 是前 2 个周期的数据,值为 `10.75`。
- `quote.close[3]` 是前 3 个周期的数据,值为 `10.50`。
---
# 迭代器
> 已弃用
迭代器是一种特殊的对象,可以用来遍历对象的集合。它在 `for` 循环中使用。
## 使用
```nvs
let a = [number] { 1, 2, 3, 4, 5 };
for (let i in a) {
// i 是 1, 2, 3, 4, 5
}
```
在这种情况下,for 实际上是调用 `a.iter().next()` 来获取迭代器。
## 方法
### next
返回迭代器中的下一个值,如果没有下一个值,它将返回 `nil`。
```nvs
let a = [number] { 1, 2, 3, 4, 5 };
let iter = a.iter();
while (iter.next()) {
// iter.value 是 1, 2, 3, 4, 5
}
```
### has_next
检查迭代器中是否有下一个值。当有下一个值时返回 `true`,否则返回 `false`。
```nvs
let a = [number] { 1, 2 }.iter();
a.has_next(); // true
a.next(); // 1
a.has_next(); // true
a.next(); // 2
a.has_next(); // false
a.next(); // nil
```
### collect
将迭代器中的所有值收集到一个[数组]中。
```nvs
let a = [number] { 1, 2, 3, 4, 5 };
let iter = a.iter();
let b = iter.collect();
// b 是 [1, 2, 3, 4, 5]
let c = iter.collect();
// c 是 []
let d = iter.next();
// d 是 nil
```
[array]: ./array
---
---
order: 4
---
# 集合
集合用于存储一组唯一的项目。
## 使用
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.insert("c");
items.insert("a");
items.len();
// 3
items.contains("a");
// true
```
与 [Array] 的行为相同,我们可以用不同的类型初始化一个集合。
```nvs
// 初始化一个数字集合
let items = set.new_number();
// 初始化一个字符串集合
let items = set.new_string();
// 初始化一个布尔集合
let items = set.new_bool();
// 初始化一个颜色集合
let items = set.new_color();
```
## 方法
### insert
将一个项目插入到集合中。
- 如果集合之前没有包含这个值,返回 `true`。
- 如果集合已经包含了这个值,返回 `false`。
```nvs
let items = set.new_string();
items.insert("a");
// true
items.insert("b");
// true
items.insert("a");
// false
items.len();
// 2
```
### remove
从集合中移除一个项目,如果项目被移除则返回 `true`,否则返回 `false`。
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.len();
// 2
items.remove("a");
// true
items.len();
// 1
items.remove("a");
// false
```
### contains
检查集合是否包含一个项目。
```nvs
let items = set.new_string();
items.insert("a");
items.contains("a");
// true
items.contains("b");
// false
```
### len
返回集合中的项目数量。
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.len();
// 2
```
### clear
从集合中移除所有项目。
```nvs
let items = set.new_string();
items.insert("a");
items.insert("b");
items.len();
// 2
items.clear();
items.len();
// 0
```
---
---
order: 999
---
# 颜色
颜色是一种表示颜色的对象类型。
## 创建颜色
```nvs
let color = #ff0000;
let color1 = #red;
```