# Core Functionality

## Variables

To create mutable variables, we use the `var` keyword. To create immutable constants, we use the `const` keyword.

```go
var x = 12
const y = 100

x = 50 // A-Okay
y = 40 // Error
```

Variables can also be declared without values (they're implicitly set as `None`):

```go
var x // Okay
const y // Error - const declarations must have a value
```

## Loops

There are two types of loops, `for` and `while`.

For loops are constructed like so:

{% code title="Basic for loop" %}

```go
for (1..100) {
    // do something
}
```

{% endcode %}

{% code title="For loop with index" %}

```go
for (1..100, index) {
    println(index)
}
```

{% endcode %}

{% code title="Index and value" %}

```go
for (1..100, index, value) {
    println(index + value)
}
```

{% endcode %}

For loops can also take lists as the first parameter:

{% code title="For loop with list" %}

```go
const someList = ["a", "b", "c"]

for (someList, index, value) {
    if (value == "b") {
        println("Found a 'b'")
    }
}
```

{% endcode %}

While loops are constructed like so:

{% code title="While loop" %}

```go
var i = 0

while (i < 10) {
    println(i)
    i += 1
}
```

{% endcode %}

Both for loops and while loops support the keywords `break` and `continue`.

`break` exits out of the current loop, and `continue` skips the current iteration.

## Branching

Vortex, like many other languages, supports branching via if/else statements:

{% code title="Branching" %}

```go
var x = 12
var y = 15

if (x > y) {
    println("x > y")
} else if (x == y) {
    println("x = y")
} else {
    println("x < y")
}
```

{% endcode %}

## Imports

We can perform imports in two separate ways: **module imports** and **variable imports**.

#### Module Imports

Module imports allow you to import an entire file (module) into the current scope. The imported module can be used as an object:

```go
import math : "../modules/math"

const res = math.fib(10)
```

#### Variable Imports

Variable imports allow you to pick and choose what you want to import into the local scope:

```go
import [PI, e, fib] : "../modules/math"

const res = PI * e + fib(20)
```

You can also import all variables from a module into the current scope by simply leaving the import list blank:

```go
import [] : "../modules/math"
```

Note that both imports have the same path, which is just a path to the module file. Do not include the extension `.vtx` in your import paths, the interpreter will do this for you under the hood.

Vortex also has a default module path it looks in if:

* No path is specified in the import
* `@modules` is used inside the import path

On Mac/Linux, the default path is in `usr/local/share/vortex/modules/<module_name>` and on Windows it's `C:/Program Files/Vortex/modules/<module_name>`.

Built-in modules will always reside in the default modules directory.

```go
import sdl // Vortex looks for "sdl/sdl.vtx" in the default dir
import sdl : "@modules/sdl" // @modules resolves to the default dir path
```

Variable imports from a built-in module (or from a module the user has placed in the default modules directory) can also target the module's name, without needing a path string:

```go
import [SDLInit] : sdl // Vortex will look for "sdl/sdl.vtx" in the default dir
```

## Data Types

There are multiple data types in Vortex that you can use in your programs:

### Number

The Number data type is implemented as a `double`, meaning it covers both integers and floating point numbers.

### String

Strings can be used to express text and support the majority of escape characters.

Strings also support interpolation:

```go
const name = "John"
const age = 34
const message = f"Hi, my name is ${name} and I am ${age} years old."

println(message)

// Hi, my name is John and I am 34 years old.
```

### Boolean

Booleans are values that can either be `true` or `false`.

### Lists

Lists are vectors that can contain any data type.

You can access list elements with the bracket syntax:

```go
var list = [1, 2, 3]

list[1] = 100

println(list) // [1, 100, 3]
```

If you attempt to access a list element using a negative index or out of range index, `None` is returned.

Setting a value using a negative index will prepend the value, similarly setting a value with an out of range index will append it:

```go
list[-1] = 20
list[100] = 30

println(list) // [20, 1, 100, 3, 30]
```

However, using the built-in append and insert functions is preferred:

```go
list.append(10)
list.insert(100, 0)

println(lists) // [100, 1, 2, 3, 10]
```

Lists can also be constructed using destructuring:

```rust
const list_a = [1, 2, 3, 4]
const list_b = [...list_a, 5, 6, 7]

println(list_b) // [1, 2, 3, 4, 5, 6, 7]
```

List destructuring (using the spread operator `...`) can only be used within another list.

### Objects

Objects are essentially maps that can hold named properties:

```go
var person = {
    firstName: "John",
    lastName: "Smith",
    age: 34,
    "fullName": () => this.firstName + " " + this.lastName,
    getAge: () => this.age
}

const name = person.fullName()
const age = person.getAge()
println(name) // John Smith
println(age) // 34
```

Notice the use of `this` in the object. Objects can refer to themselves within their own properties, but only in the context of a function.

Object properties can be constructed using both strings and identifiers.

Properties can be accessed in two ways, through the bracket notation or through the dot notation:

```
println(person["fullName"]) // "John Smith"
println(person.age) // 34
println(person.height) // None
```

Notice how attempting to access a property that does not exist simply returns `None`.

Object construction can also be done with object destructuring:

```go
const obj_a = {
    a: 0,
    b: 1,
    c: 2
}

const obj_b = {
    ...obj_a,
    c: 2.5,
    d: 3,
    e: 4
}

println(obj_b) // { a: 0, b: 1, c: 2.5, d: 3, e: 4 }
```

Object destructuring (using the spread operator `...`) can only be used within another object.

### None

None is a special data type that refers to a variable that has no value.

### Pointer

Pointer types are used to store raw C pointers. They are predominately used when dealing with external C modules.

Vortex does not support raw memory access, and so these types are reserved mainly for passing around pointers to and from dynamic libraries.

### Function

Functions are also core types in Vortex, and can be passed around like other variables.

Functions can act as standard subroutines or as coroutines: [Read more about coroutines here](/vortex-docs/language-reference/coroutines.md).

There is no function keyword in Vortex. All functions are lambdas assigned to variables.

```rust
const sayHi = (name) => println(f"Hello, ${name}!")

sayHi("James") // Hello, James!
```

Function parameters can have defaults, however you cannot declare non-default parameters after the default ones:

```rust
const add = (a, b = 10) => a + b

add(1, 2) // 3
add(10) // 20
```

You can declare a variadic parameter that will capture a variable amount of values. This parameter has to be the last declared parameter:

```rust
const doStuff = (...args) => {
    println(args)
}

doStuff(1, "a", "b", 3) // [1, "a", "b", 3]
```

The variables passed in become a list that can then be manipulated.

Another useful thing about variadic parameters is that you can use them to pass an unknown amount of arguments to inner functions:

```rust
const outerFunc = (innerFunc, ...args) => {
    const start = clock()
    const res = innerFunc(...args)
    const end = clock() - start
    return [res, end * 1000]
}

const inner = (a, b, c) => {
    return a + b + c
}

outerFunc(inner, 5, 4, 5).println()

// [14, 0.009]
```

In the case above, `outerFunc` doesn't need to care about how many arguments `innerFunc` takes. It simply takes the arguments in args passes them down to the provided function.

If no return value is declared, the function will return `None`.

#### Functions As Constructors

Functions are also used as constructors.

Vortex does not support classes, however class functionality can be achieved by using functions instead:

{% code overflow="wrap" %}

```rust
type Color = (r, g, b, a = 255) => ({
    r: r,
    g: g,
    b: b,
    a: a
})

const color = Color(144, 28, 42)

println(info(color).typename) // Color
```

{% endcode %}

Constructor functions simply return objects. However, note that the constructor wasn't defined with `var` or `const`. We used the `type` keyword here instead.

There's nothing magical about this keyword. It simply ensures that the object we return from the function receives the same typename as the constructor's name.

Getting the object's typename (via the info() built-in), we can see that it is indeed `Color`.

We're not limited to returning an object right away. Remember that Constructors are just functions, so we could have done this instead:

```rust
type Color = (r, g, b, a = 255) => {
    const cap = (n) => {
        if (n < 0) {
            return 0
        }
        if (n > 255) {
            return 255
        }
        return n
    }
    
    return {
        r: cap(r),
        g: cap(g),
        b: cap(b)
    }
}

const color = Color(144, 28, 42)
```

Instead of returning an object directly, we declared a `cap` function and returned an object with capped values. And since we declared the constructor with the `type` keyword, the returned object will have the typename `Color`.

#### Uniform Function Call Syntax

Vortex supports UFCS, allowing you to chain functions together in a seamless way.

Below is a simple example of showing the difference between normal function calls and UFCS:

```rust
const join = (a, b) => a + b

// Normal call
join("Hello", " world!") // "Hello world!"

// UFCS
"Hello".join(" world!") // "Hello world!"
```

This doesn't look too useful yet, however if we wanted to call this function multiple times to construct a longer sentence, UFCS remains easier to read and write:

{% code overflow="wrap" %}

```rust
// Normal call
join(join(join(join("Hello", " world!"), " My name is"), " Jordan"), " and I am 32 years old.") // "Hello world! My name is Jordan and I am 32 years old"

// UFCS
"Hello".join(" world!").join(" My name is").join(" Jordan").join(" and I am 32 years old.") // "Hello world! My name is Jordan and I am 32 years old"

/* Let's make that even clearer */

"Hello"
.join(" world!")
.join(" My name is")
.join(" Jordan")
.join(" and I am 32 years old.")
```

{% endcode %}

As you can see, the UFCS version is much more readable. And not only that, but it's much easier to modify. We can swap the calls to join around, add new ones in between, or remove some without dealing with nested calls.

Every function in Vortex can be called in this manner.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dibs.gitbook.io/vortex-docs/language-reference/core-functionality.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
