Introduction
Welcome to the Dyon 0.39 tutorial!
Dyon is a dynamically typed scripting language, designed for game engines and interactive applications. It was started in 2016 by Sven Nilsen.
This tutorial is written for people with little programming experience.
The language in this tutorial is made simple on purpose. If you are experienced programmer, you might want to skip to the parts that interests you.
Dyon is different!
If you have used another programming language before, there are some things worth to keep in mind:
Dyon has a limited memory model because of the lack of a garbage collector. The language is designed to work around this limitation.
The language takes ideas from Javascript, Go and Rust, but focuses on practical features for making games:
- Optional type system with ad-hoc types
- Similar object model to Javascript, but without
null
- Built-in support for 4D vectors, HTML hex colors
- Go-like coroutines
There are plenty of new ideas in Dyon:
- Lifetime check for function arguments
- Use
return
as local variable - Mathematical loops
- Current objects
- Secrets
- Grab expressions
- Dynamic modules as a way of organizing code
How this tutorial is organized
At the bottom there is a "Comments" section. This contains things you might have noticed and things you can try out on your own.
Source code
You will find the source code for all examples under "examples" in the git repository.
Getting started
Before you can run Dyon programs, you need to do the following:
- Install Rust.
- Open up the Terminal window
- Type
cargo new --bin <project name>
Open up "Cargo.toml" and add the following:
[dependencies]
dyon = "0.36.0"
Open up "src/main.rs" and type:
extern crate dyon; use dyon::{error, run}; fn main() { error(run("src/main.dyon")); }
Hello World example
Source code: examples/hello_world
Put this in "src/main.dyon":
fn main() { println("Hello world!") }
In the terminal window, go to the "hello_world" folder and type:
$ cargo run
This should print out:
Hello world!
Comments
You might have noticed:
- Dyon has no ";" at the end of lines
- The "fn" keyword is used by both Rust and Dyon
- The "main" function is called when running the program
- Double quotes "" are used for text
Some things you can try:
- Put your name inside the text!
- Add a new line where you print out something else!
- Change "println" to "print" and see what happens!
Comments
A comment is text to explain how some code works. Comments are ignored when running the program.
Dyon uses //
for single-line and /* */
for multi-line comments.
/* This text is inside a multi-line comment. It is ignored when running the program. */ fn main() { // Can I ask you something? println("hello?") }
A single-line comment ignores the rest of the line:
#![allow(unused)] fn main() { println("hello") // Prints `hello`. }
A multi-line comment starts with /*
and ends with */
.
#![allow(unused)] fn main() { /* testing, testing! */ println("hello") }
You can nest /* */
:
#![allow(unused)] fn main() { /* /* A comment inside a comment! */ */ }
Tips and tricks
It is more common to use //
than /* */
for documenting the code.
End a comment with a dot to make it easier to see where the line is ending:
#![allow(unused)] fn main() { // Alice opened the door // by pressing a button. }
You can use /* */
to ignore some code without removing it:
/* fn main() { println("one") } */ fn main() { println("two") }
One technique that helps making code more understandable: Organize the code in paragraphs, like in a book. Write a single-line comment for each paragraph. Separate paragraphs with an empty line.
fn main() { // Print the numbers from 1 to 10. list := sift i 10 { i + 1 } println(list) // Print the numbers from 11 to 20. list := sift i [10, 20) { i + 1 } println(list) }
Functions
In Dyon, there are two ways to declare a function.
One way is to use fn
to define a function:
#![allow(unused)] fn main() { fn f(x) -> { return x + 1 } }
If a function does not return a value, you leave out ->
:
#![allow(unused)] fn main() { fn say_hi() { println("hi!") } }
Another way is to use mathematical notation:
#![allow(unused)] fn main() { f(x) = x + 1 }
All mathematically declared functions returns a value.
Pro tip: To declare constants, use mathematical notation:
#![allow(unused)] fn main() { // Speed of light. c() = 299_792_458 }
Fibonacci example
Source code: examples/fibonacci
fib(x) = if x <= 0 { 0 } else if x == 1 { 1 } else { fib(x-1) + fib(x-2) } fn main() { for i 20 { println(fib(i)) } }
Do not worry if you do not understand the code above. This is just to show how to declare a function and call it.
Mutability
In Dyon, when a function changes an argument, you need to add mut
:
fn foo(mut x) { x = 3 } fn main() { a := 2 foo(mut a) println(a) // prints `3` }
A function that calls a function that changes an argument, also need mut
:
fn foo(mut x) { x = 3 } // Requires `mut x` because it calls `foo`. fn bar(mut x) { foo(mut x) } fn main() { a := 2 bar(mut a) println(a) // prints `3` }
This helps programmers understand the code.
Mutability information is part of the function name. You can declare multiple functions with different mutability patterns:
// `name` name(person: {}) = clone(person.name) // `name(mut,_)` fn name(mut person, name) { person.name = clone(name) } fn main() { character := {name: "Homer Simpson"} println(name(character)) // prints `Homer Simpson` name(mut character, "Marge Simpson") println(name(character)) // prints `Marge Simpson` }
Named arguments
In Dyon, you can call some functions in two ways:
#![allow(unused)] fn main() { attack__player_enemy(mut player, enemy) attack(player: mut player, enemy: enemy) }
The named arguments are part of the function name.
- Double underscores separates function name from arguments
- Arguments are separated by a single underscore
It is common to use named arguments when there are lots of parameters.
Return
In Dyon, return
can be used as a variable:
#![allow(unused)] fn main() { fn foo(mut a: f64) -> { return = a + 2 a += 2 } }
If you leave out =
, the function will exit with the value:
#![allow(unused)] fn main() { fn foo(a: f64) -> { return a + 2 println("hi") // never gets called. } }
Functions without ->
can use return
without a value:
#![allow(unused)] fn main() { fn foo() { return } }
All functions that returns a value must use return
.
You get an error if you forget it:
#![allow(unused)] fn main() { fn foo(a: f64) -> { a + 2 } // ERROR }
--- ERROR ---
In `source/test.dyon`:
Type mismatch (#775):
Expected `any`, found `void`
1,1: fn foo(a: f64) -> { a + 2 }
1,1: ^
Blocks
A block starts with {
and ends with }
:
#![allow(unused)] fn main() { a := { println("hi!") 5 } println(a) // prints `5` }
The last expression in a block becomes the result of block expression.
When declaring a variable inside a block, you might get an error:
#![allow(unused)] fn main() { a := { b := 5 b // ERROR } }
--- ERROR ---
In `source/test.dyon`:
`b` does not live long enough
2,10: a := {
2,10: ^
3,1: b := 5
4,1: b // ERROR
5,1: }
Dyon has no garbage collector, but uses a lifetime checker instead. This is explained later in the chapter "Lifetimes".
This means that "b" only lives for the scope of the block,
and you must use clone
to fix it:
#![allow(unused)] fn main() { a := { b := 5 clone(b) // OK } }
When you do a math operation, Dyon knows it is safe:
#![allow(unused)] fn main() { a := { b := 5 b + 2 // OK } }
Variables
#![allow(unused)] fn main() { a := 3 // Create a variable "a" with value `3`. a = 4 // Change the value of variable "a" to `4`. }
A variable has a name, a type and a value.
- The operator
:=
creates a variable. - The operator
=
changes the value of a variable.
When changing the value of a variable, the type must be the same:
#![allow(unused)] fn main() { a := 3 a = "hi!" // ERROR }
--- ERROR ---
main (source/test.dyon)
Expected assigning to text
3,5: a = "hi!" // ERROR
3,5: ^
Echo example
Source code: examples/echo
Create a new project and name it "echo".
Put this in "src/main.dyon":
fn main() { print("Type something: ") line := read_line() sleep(1) println(line) }
This program prints out the text you gave it after 1 second. The text is stored inside a variable "line".
Comments
You might have noticed:
println
ends with a new line, whileprint
stays at same line
Some things you can try:
- Print out "You said: (line)"!
- Change
sleep(1)
to wait 5 seconds! - Change the name of "line" to something else!
- Put "println(line)" first and see what happens!
Numbers
A number in Dyon is a variable that stores floats with 64 bit precision.
The type is f64
.
#![allow(unused)] fn main() { a := 1 b := .5 c := 2.5 d := -3 e := 1_000_000 f := 1e6 g := -7_034.52e-22 }
You can add, subtract, multiply, divide, take the power and find the division reminder:
#![allow(unused)] fn main() { a := 2 b := a + 3 c := a - 3 d := a * 3 e := a^3 f := a / 3 g := a % 3 }
Relative change
The following operators change the value relatively:
#![allow(unused)] fn main() { a += 1 // increase by 1 a -= 1 // declare by 1 a *= 2 // multiply by 2 a /= 2 // divide by 2 a %= 2 // find division reminder of 2 }
Comments
Some thing you might have noticed:
f64
is used by both Rust and Dyon- "One million" can be written as
1_000_000
or1e6
Strings
A string in Dyon is a variable that stores UTF-8 bytes.
This represent text.
The type is str
.
Use double quotes to write a string:
#![allow(unused)] fn main() { a := "hi!" }
The \
symbol escapes special characters:
\n
- New line\t
- Tab\"
- The"
character\\
- The\
character\r
- Moves to beginning of line without creating a new line\u005c
- Unicode character by 4 hexadecimal code
The +
operator joins two strings together:
#![allow(unused)] fn main() { name := "Santa" title := "hi " + name }
The str
function converts a value into a string:
#![allow(unused)] fn main() { age := 58 println("Bilbo is " + str(age) + " years old!") }
Some useful functions for strings
fn str(any) -> str
- convert a variable to a stringfn read_line() -> str
- read line from standard inputfn trim(str) -> str
- trims away whitespace at both sidesfn trim_left(str) -> str
- trims away whitespace at left sidefn trim_right(str) -> str
- trims away whitespace at right sidefn json_string(str) -> str
- creates a JSON stringfn str__color(vec4) -> str
- HTML hex color stringfn chars(str) -> [str]
- characters of a string
Booleans
A boolean in Dyon is a variable that stores true
or false
.
This represents a choice with two options.
The type is bool
.
Lazy and eager
A lazy operator does not compute the right argument if the left argument gives the result.
An eager operator computes both arguments.
Logical gates
A logical gate is an operation on two boolean values.
A | B | A AND B | A OR B | A XOR B | A EXC B |
---|---|---|---|---|---|
false | false | false | false | false | false |
false | true | false | true | true | false |
true | false | false | true | true | true |
true | true | true | true | false | false |
There are 4 operators for AND:
#![allow(unused)] fn main() { a && b // lazy a and b // eager a ∧ b // eager a * b // eager }
There are 4 operators for OR:
#![allow(unused)] fn main() { a || b // lazy a or b // eager a ∨ b // eager a + b // eager }
There is 1 operator for XOR:
#![allow(unused)] fn main() { a ^ b }
There is 1 operator for EXC:
#![allow(unused)] fn main() { a - b }
There are 2 operators for NOT:
#![allow(unused)] fn main() { !a ¬a }
Why more than one way
Boolean algebra is used with different notations:
- Programming applications
- Logic
- Non-invertible sets
When programming applications, you use &&
, ||
and !
.
Logic use ∧
, ∨
and ¬
.
Non-invertible sets uses *
, +
and -
.
In the future Dyon might get more features for Boolean algebra.
4D vectors
In Dyon, you can compute with 4D vectors:
(x, y, z, w)
Many operations are built-in for working with 4D vectors:
#![allow(unused)] fn main() { a := (1, 2, 3, 4) b := (5, 6, 7, 8) println(a + b) // prints `(6, 8, 10, 12)` }
The components z
and w
are set to 0 by default:
#![allow(unused)] fn main() { a := (1, 2) }
The y
component can also be set to 0, but requires ",":
#![allow(unused)] fn main() { a := (1,) }
HTML hex colors
A HTML hex color is converted into a 4D vector:
a := #ff0000 // red
b := #00ff00 // green
c := #0000ff // blue
d := #00000033 // semi-transparent black
Swizzle components
To swizzle components, you can use this notation:
#![allow(unused)] fn main() { a := (1, 2) b := (yx a,) }
You can repeat a component up to 4 times:
#![allow(unused)] fn main() { a := (1, 2) b := (yyyy a,) println(b) // prints `(2, 2, 2, 2)` }
Calling functions
When calling a function, you can unpack vector components:
add(x, y) = x + y fn main() { a := (1, 2) println(add(xy a)) // prints `3` }
If a function takes named arguments, you can use this trick:
add__x_y(x, y) = x + y fn main() { a := (1, 2) println(add(x_y: xy a)) // prints `3` }
Addition and multiplication
Addition and multiplication is per component for two vectors.
#![allow(unused)] fn main() { (1, 2) + (3, 4) // `(4, 6)` (1, 2) * (3, 4) // `(3, 8)` }
Scalar addition, multiplication and division is allowed on both sides and is per component.
#![allow(unused)] fn main() { (1, 2) + 1 // `(2, 3, 1, 1)` 3 + (1, 2) // `(4, 5, 3, 3)` (1, 2) * 2 // `(2, 4)` 2 * (1, 2) // `(2, 4)` (1, 2) / 2 // `(0.5, 1)` 2 / (1, 2, 4, 8) // `(2, 1, 0.5, 0.25)` }
Dot product
Dot product of two vectors can be written in two ways:
#![allow(unused)] fn main() { a := (1, 2) b := (3, 4) println(a *. b) println(a · b) }
Cross product
Cross product of two vectors can be written in two ways:
#![allow(unused)] fn main() { a := (1, 2) b := (3, 4) println(a x b) println(a ⨯ b) }
Norm
The norm of a vector, also called "length" or "magnitude":
#![allow(unused)] fn main() { a := (3, 4) println(|a|) }
Un-loops
The vec4
, vec3
, vec2
are un-rolled and replaces the index with a number:
fn main() { a := vec4 i { i + 1 } println(a) // prints `(1, 2, 3, 4)` }
You can check this by printing out a closure:
fn main() { a := \() = vec4 i { i + 1 } // prints `\() = ({ 0 + 1 }, { 1 + 1 }, { 2 + 1 }, { 3 + 1 })` println(a) }
Other functions for 4D vectors:
fn x(vec4) -> f64
- get x componentfn y(vec4) -> f64
- get y componentfn z(vec4) -> f64
- get z componentfn w(vec4) -> f64
- get w componentfn s(vec4, f64) -> f64
- get vector component by indexfn dir__angle(f64) -> vec4
- rotation vector around Z axis
Precision
Whenever you do calculations with 4D vectors, you get float 32 bit precision.
4D vectors
In Dyon, you can compute with 4D matrices:
mat4 {
m00, m01, m02, m03;
m10, m11, m12, m13;
m20, m21, m22, m23;
m30, m31, m32, m33;
}
Many operations are built-in for working with 4D matrices:
fn main() {
a := mat4 {1,;}
b := mat4 {1,;}
// prints `mat4 {2,0,0,0; 0,2,0,0; 0,0,2,0; 0,0,0,2}`
println(a + b)
}
4D vectors used as rows:
mat4 {(1, 0, 0, 0); (0, 1, 0, 0); (0, 0, 1, 0); (0, 0, 0, 1)}
Omitted parentheses:
mat4 {1,0,0,0; 0,1,0,0; 0,0,1,0; 0,0,0,1}
Omitted trailing zeroes:
mat4 {1,; 0,1; 0,0,1; 0,0,0,1}
Fills out rows corresponding to identity matrix:
mat4 {1,; 0,1; 0,0,1}
Identity matrix:
mat4 {1,;}
Addition and multiplication
Addition is per component for two matrices.
Multiplication is matrix multiplication.
fn main() {
a := mat4 {1,;}
b := a + a
// Prints `mat4 {2,0,0,0; 0,2,0,0; 0,0,2,0; 0,0,0,2}`.
println(b)
// Prints `mat4 {4,0,0,0; 0,4,0,0; 0,0,4,0; 0,0,0,4}`.
c := b * b
println(c)
}
+=
, -=
and *=
:
fn main() {
a := mat4 {1,;}
a += a
// Prints `mat4 {2,0,0,0; 0,2,0,0; 0,0,2,0; 0,0,0,2}`.
println(a)
// Prints `mat4 {4,0,0,0; 0,4,0,0; 0,0,4,0; 0,0,0,4}`.
a *= a
println(a)
// Prints `mat4 {3,0,0,0; 0,3,0,0; 0,0,3,0; 0,0,0,3}`
a -= mat4 {1,;}
println(a)
}
+
, -
and *
with scalars and matrices is per component:
fn main() {
a := mat4 {1,2,3,4;2,3,4,1;3,4,1,2;4,1,2,3}
println(2 * a)
println(a * 2)
println(2 + a)
println(a + 2)
println(2 - a)
println(a - 2)
}
Transform 4D vector by multiplying a 4D matrix with a vec4
that has a zero in the 4th component:
fn main() {
// Scale x-axis up 2 times.
a := mat4 {2,;}
println(a * (1, 1, 1))
// Scale y-axis up 2 times.
a := mat4 {1,; 0,2}
println(a * (1, 1, 1))
// Scale z-axis up 2 times.
a := mat4 {1,; 0,1; 0,0,2}
println(a * (1, 1, 1))
// The same using `scale`.
println(scale((1, 1, 2)) * (1, 1, 1))
}
Transform a point by multiplying a 4D matrix with a vec4
that has a one in the 4th component:
fn main() {
pos := (1, 2, 3)
// Put `1` in the 4th component to transform a point.
println(mov((1, 2)) * (xyz pos, 1))
}
Get row vectors with rx
, ry
, rz
, rw
, rv
and get column vectors with cx
, cy
, cz
, cw
, cv
:
fn main() {
a := mat4 {
1,2,3,4;
5,6,7,8;
9,10,11,12;
13,14,15,16;
}
// Print row vectors.
println(rx(a)) // Prints `(1, 2, 3, 4)`.
println(ry(a)) // Prints `(5, 6, 7, 8)`.
println(rz(a)) // Prints `(9, 10, 11, 12)`.
println(rw(a)) // Prints `(13, 14, 15, 16)`.
// Print row vectors using a loop.
for i 4 {println(rv(a, i))}
// Print column vectors.
println(cx(a)) // Prints `(1, 5, 9, 13)`
println(cy(a)) // Prints `(2, 6, 10, 14)`
println(cz(a)) // Prints `(3, 7, 11, 15)`
println(cw(a)) // Prints `(4, 8, 12, 16)`
// Print column vectors using a loop.
for i 4 {println(cv(a, i))}
}
Negation
Negation is per component:
a := mat4 {1,;}
// Prints `mat4 {-1,0,0,0; 0,-1,0,0; 0,0,-1,0; 0,0,0,-1}`
println(-a)
Other functions for 4D vectors:
fn det(m: mat4) -> f64
determinantfn inv(m: mat4) -> mat4
inversefn mov(v: vec4) -> mat4
translationfn scale(v: vec4) -> mat4
scalefn rot__axis_angle(axis: vec4, angle: f64) -> mat4
axis-angle rotationn ortho__pos_right_up_forward(pos: vec4, right: vec4, up: vec4, forward: vec4) -> mat4
orthogonal viewfn proj__fov_near_far_ar(fov: f64, near: f64, far: f64, ar: f64) -> mat4
projection viewfn mvp__model_view_projection(model: mat4, view: mat4, projection: mat4) -> mat4
model-view-projection
Precision
Whenever you do calculations with 4D matrices, you get float 32 bit precision.
Arrays
An array stores a list of values. The type is []
.
#![allow(unused)] fn main() { a := [1, 2, 3] }
You can store values of different types in the same array:
#![allow(unused)] fn main() { a := [1, "hi", [1, 2, 3]] }
To access an value inside an array, you use a number that start at 0
:
#![allow(unused)] fn main() { a := [1, 2, 3] println(a[0]) // prints `1` println(a[1]) // prints `2` println(a[2]) // prints `3` }
Multi-dimensional arrays
An array can contain arrays. This makes it possible to represent grids in 2D, 3D etc.
#![allow(unused)] fn main() { a := [1, 2, 3] // 1D println(a[0]) // prints `1` b := [ // 2D [1, 2, 3], [4, 5, 6], [7, 8, 9], ] println(b[0][0]) // prints `1` c := [ // 3D [[1, 2], [3, 4]], [[5, 6], [7, 8]], ] println(a[0][0][0]) // prints `1` }
Memory
Each item in an array takes 24 bytes (64 bit platforms):
- 8 bytes for type information
- 16 bytes for item data
This is optimized to store 4D vectors.
Objects
An object stores a list of values, organized as a dictionary. The type is {}
.
#![allow(unused)] fn main() { a := {x: 1, y: "hi"} }
To change a value in an object:
#![allow(unused)] fn main() { a.x = 2 }
Alternative:
#![allow(unused)] fn main() { a["x"] = 2 }
The value must be of the same type.
If you want to change the type, use :=
:
#![allow(unused)] fn main() { a.x := "hi!" }
Insert new keys and values with :=
:
#![allow(unused)] fn main() { a := {} a.x := 1 a.y := 2 a.z := 3 }
Some useful functions
fn has({}, str) -> bool
- returntrue
if object has keyfn keys({}) -> [str]
- return keys of object
Links
A link in Dyon is a variable that stores bool
, f64
and str
efficiently.
It is called "link" because it is fast and easy to put together data.
#![allow(unused)] fn main() { name := "John" a := link {"Hi "name"!"} }
The data inside a link can not be changed.
You can read the data using head
and tail
:
#![allow(unused)] fn main() { a := link { 1 2 3 } b := head(a) // `some(1)` c := tail(a) // `link { 2 3 }` }
When you put a link inside a link, it gets flattened:
#![allow(unused)] fn main() { a := link { 1 2 3 } // `link { "start "1 2 3" end" }` b := link { "start "a" end" } }
When to use links
Links are often used to:
- Generate lots of data and then convert to
str
- Pre-process parts of a text template
- Generate a web page
- Code generation
- Store lots of
f64
,bool
orstr
in memory
Because of the easy syntax for links, you can use Dyon as a template language.
Memory
The memory of a link is divided into blocks of 1024 bytes (64 bit platforms).
A link takes only 3.2% extra memory than the ideal amount when blocks are filled.
To save memory with a link compared to an array, you need 42 items:
#![allow(unused)] fn main() { a := link { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 } }
Other operators
You can use +=
(back) and -=
(front) to link together link blocks.
This is faster, but uses a little more memory.
You can not use +
because it is too easy to waste memory by error.
Some useful functions
fn is_empty(link) -> bool
- returntrue
if link is emptyfn head(link) -> opt[any]
- return first item in linkfn tail(link) -> link
- return whole link except first itemfn tip(link) -> opt[any]
- return last item in linkfn neck(link) -> link
- return whole link except last item
Closures
A closure in Dyon is a variable that works similar to a mathematical function.
#![allow(unused)] fn main() { a := \(x) = x + 1 }
You can print out a closure:
#![allow(unused)] fn main() { // prints `\(x: any) = x + 1` println(\(x) = x + 1) }
To call a closure, use the \
character before the name:
#![allow(unused)] fn main() { a := \(x) = x + 1 println(\a(0)) // prints `1` }
Calling closures on objects
When an object stores a closure, you can call it with named arguments:
fn main() { gollum := {say__msg: \(msg) = "My precious " + msg} // prints `my precious ring` println(\gollum.say(msg: "ring")) }
Grab
Use grab
to compute a value and insert it into a closure:
#![allow(unused)] fn main() { a := 2 b := \(x) = (grab a + 2) + x // prints `\(x: any) = 4 + x` println(b) }
When a closure is inside another closure, you can use grab '2
:
#![allow(unused)] fn main() { a := 2 b := \(x) = \(y) = (grab '2 a) + (grab x) + y // prints `\(x: any) = \(y: any) = 2 + (grab x) + y` println(b) // prints `\(y: any) = 2 + 0 + y` println(\b(0)) }
You can use grab 'N
where N
is the level you want to compute from.
Pro tip: Pre-compute as much as possible with grab
to improve performance.
Options
In Dyon, optional values are wrapped in some(x)
or none()
.
The type is opt
, which defaults to opt[any]
.
The unwrap
function returns the value inside some(x)
.
fn main() { a := some(5) if a != none() { println(unwrap(a)) // prints `5` } }
Note to programmers accustomed to null
Many languages uses null
or nil
to indicate an empty reference.
This leads to lots of maintenance problems, because it can appear anywhere.
Dyon uses opt
whenever you would use null
in another language.
This forces programmers to deal with it explicitly,
which reduces number of bugs in the program.
Some useful functions
fn some(any) -> opt[any]
fn none() -> opt[any]
fn unwrap(any) -> any
Results
In Dyon, results are wrapped in ok(x)
or err(x)
.
The type is res
which defaults to res[any]
.
The error type is always any
.
fn main() { a := ok(5) if is_ok(a) { println(unwrap(a)) // prints `5` } }
Result is used to handle errors explicitly.
Dyon has an operator ?
to make this easier.
You can read more about this in the chapter "Error handling".
Some useful functions
fn unwrap(any) -> any
fn unwrap_err(any) -> any
fn is_ok(res[any]) -> bool
fn is_err(res[any]) -> bool
fn ok(any) -> res[any]
fn err(any) -> res[any]
Threads
In Dyon, a thread is created with the go
keyword.
The type is thr
, which defaults to thr[any]
.
fn find_sum(n: f64) -> f64 { return sum i n { i + 1 } } fn main() { a := go find_sum(1_000_000) println(unwrap(join(thread: a))) // prints `500000500000` }
A thread runs in parallel.
Current objects are not passed between threads
Dyon does a clone of variables that are passed to a go
call.
The new thread starts with an empty stack.
This means that current objects are not shared between threads.
There can only be one reference when joining
A thread must only have a single reference to it when joining.
For example, if you store threads in an an array, you need to use pop
.
Source code: examples/multi_threads
foo(i) = i + 40 fn main() { a := sift i 3 {go foo(i)} for i len(a) { println(unwrap(join(thread: pop(mut a)))) } }
Some useful functions
fn join__thread(thr[any]) -> res[any]
- waits for the thread to finish, then returns the result
Secrets
In Dyon, a secret is a hidden array of values associated with a bool
or f64
.
The type is sec[bool]
or sec[f64]
.
Secrets are used in combination with any
/all
/max
/min
loops.
This feature helps developers to write short and correct code.
For example, you can use secrets to find the first item in a list that satisfies a condition:
#![allow(unused)] fn main() { list := [1, 2, 3] a := any i { list[i] > 2 } if a { println(why(a)) // prints `[2]` because `list[2] == 3` and `3 > 2`. } }
It also works for lists inside lists:
#![allow(unused)] fn main() { list := [[1, 2], [3, 4]] a := any i, j { list[i][j] > 2 } if a { println(why(a)) // prints `[1, 0]` because `list[1][0] == 3` and `3 > 2`. } }
max
and min
loops uses the where
keyword to unlock the secret:
#![allow(unused)] fn main() { list := [1, 2, 3, 4] a := max i { list[i] } println(where(a)) // prints `[3]` because `list[3]` is the greatest number. }
Secrets are used to solve problems that otherwise would be a bit tricky to code. For example, the "minimax" algorithm which is used to pick the best move when playing a zero-sum game:
#![allow(unused)] fn main() { payoff := [[0, 1], [2, -1]] a := min i {max j { payoff[i][j] }} println(where(a)) // prints `[0, 1]` because that is the best move. }
In the beginning secrets might feel a bit "backwards".
Instead of reducing a query into a bool
, one can extract information from them!
Do not worry, because you will get used to it and you will like this feature
when programming complex logic.
Use explain_why
to add a secret to a bool
:
#![allow(unused)] fn main() { a := explain_why(true, "hi!") if a { println(why(a)) // prints `["hi!"]` } }
Use explain_where
to add a secret to a f64
:
#![allow(unused)] fn main() { a := explain_where(2.5, "giant") println(where(a)) // prints `["giant"]` }
A secret propagates from the left argument of a binary operator:
#![allow(unused)] fn main() { a := explain_where(2.5, "giant") is_tall := a > 2.0 if is_tall { println(why(is_tall)) // prints `["giant"]` } }
When using a min
, max
, any
or all
, the indices are automatically added as secrets:
#![allow(unused)] fn main() { list := [[1, 2], [3, 4]] println(why(any i, j { list[i][j] > 2 })) // prints `[1, 0]` }
A secret must have meaning
The function why
will only work if the value is true
.
This prevents programs that do not make sense, such as:
#![allow(unused)] fn main() { list := [1, 2, 3] println(why(all i { list[i] < 10 })) }
--- ERROR ---
main (source/test.dyon)
why
This does not make sense, perhaps an array is empty?
3,17: println(why(all i { list[i] < 10 }))
3,17: ^
Likewise, where
will only work if the value is not NaN
(0/0):
#![allow(unused)] fn main() { list := [] println(where(min i { list[i] })) }
--- ERROR ---
main (source/test.dyon)
This does not make sense, perhaps an array is empty?
3,19: println(where(min i { list[i] }))
3,19: ^
Remember to check for empty arrays!
Overhead
Since Dyon supports 4D vectors, the size of the Variable
enum is quite large.
This means better performance for 4D vectors but slower performance for bool
and f64
.
Dyon is designed for game development so this is a reasonable trade-off.
The overhead from executing extra instructions for secrets is not that large because of good cache locality.
If
In Dyon, the if
keyword runs a block if its condition is true:
#![allow(unused)] fn main() { a := 3 < 4 if a { println("hi!") } }
An else
block runs when previous conditions are false:
#![allow(unused)] fn main() { a := 3 > 4 if a { println("yes!") } else { println("no...") } }
An else if
block runs when previous conditions are false and its condition is true:
#![allow(unused)] fn main() { a := 2 if a == 0 { println("=0") } else if a == 1 { println("=1") } else if a == 2 { println("=2") } else { println(">2") } }
An if
can be used as an expression:
#![allow(unused)] fn main() { a := 3 b := 3 c := 2 + if a == b { 1 } else { 0 } }
Loops
In Dyon there are 4 kinds of loops:
- Infinite loop
- Traditional For loop
- Mathematical loops
- Link loop
Infinite loop
#![allow(unused)] fn main() { loop { println("hi!") sleep(1) } }
An infinite loop runs runs forever, or until break
is used inside it.
Tip: Use Ctrl+C to terminate an infinite loop in the Terminal window.
Traditional For loop
#![allow(unused)] fn main() { for i := 0; i < 10; i += 1 { println(i) } }
A traditional loop is similar to the loop in the programming language C. It takes 3 expressions:
- Initialize, called first and once, e.g.
i := 0
- Condition, checked for each turn, e.g.
i < 10
- Step, called after each turn, e.g.
i += 1
Mathematical loops
Dyon is famous for its mathematical loops:
for
- do something for each turnsift
- create an array out of valuesmin
- find the minimum valuemax
- find the maximum value∃
/any
- check whether a condition is true for any value∀
/all
- check whether a condition is true for all values∑
/sum
- add values to get the sum∏
/prod
- multiply values to get the product∑vec4
/sum_vec4
- add 4D vectors to get the sum∏vec4
/prod_vec4
- multiply 4D vectors to get the product
In mathematics, it is very common to loop over an index. An index starts at 0, and increases with 1 for each turn. All mathematical loops uses the same index notation.
#![allow(unused)] fn main() { for i 10 { println(i) } }
This is equivalent to for i := 0; i < 10; i += 1
.
You can also specify the start and end value:
#![allow(unused)] fn main() { for i [0, 10) { println(i) } }
Link loop
The link loop is similar to a link block, but for a repeated pattern.
#![allow(unused)] fn main() { list := [1, 2, 3] println(link i {(i+1)": "list[i]}) }
All evaluated expressions are appended to the link. Expressions that do not return a value are allowed inside a link loop.
Pro-tip: A link block inside a link loop gives you an all-or-nothing behavior.
fn main() { people := [{name: "Homer"}, {name: "Bart"}, {name: "Marge"}] kids := link i {link { "name: " name := people[i].name if name == "Bart" {continue} else {name} "\n" }} print(kids) }
Break
The break
keyword exits a loop:
#![allow(unused)] fn main() { a := 4 loop { a -= 1 if a < 0 { break } } }
To exit an outer loop, use a label:
#![allow(unused)] fn main() { 'outer: loop { loop { break 'outer } } }
In a mathematical loop, break
skips the rest:
fn main() { list := [1, 2, 3] println(sum i { if i > 1 { break } // skip `3` list[i] }) // prints `3` }
Continue
The continue
keyword goes to the next turn.
#![allow(unused)] fn main() { loop { sleep(1) continue println("hi") // never called } }
To go to the next turn of an outer loop, use a label:
#![allow(unused)] fn main() { 'outer: loop { loop { continue 'outer } } }
In a mathematical loop, continue
skips the current item:
fn main() { list := [1, 2, 3, 4] println(sum i { if i == 2 { continue } // skip `3` list[i] }) // prints `7` }
Infer range
In Dyon, the range of a mathematical loop can be inferred from the loop body.
When iterating over a list, you can leave out start and end:
#![allow(unused)] fn main() { list := [1, 2, 3] for i { println(list[i]) } }
This is equivalent to:
#![allow(unused)] fn main() { list := [1, 2, 3] for i len(list) { println(list[i]) } }
For nested loops, the indices must follow the same order as the loops:
#![allow(unused)] fn main() { list := [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] for i { for j { for k { println(list[i][j][k]) } } } }
Packed loops
In Dyon, you can pack mathematical loops together of the same kind:
#![allow(unused)] fn main() { for i, j, k { println(list[i][j][k]) } }
This is equivalent to:
#![allow(unused)] fn main() { for i { for j { for k { println(list[i][j][k]) } } } }
You can also specify start and end for each index:
fn main() { list := [1, 2, 3, 4] n := len(list) // For each pair. for i n, j [i + 1, n) { println(abs(list[i] - list[j])) } }
Pro tip: If you find a packed loop hard to understand, you can print out closure to see how it works:
fn main() { a := \() = { list := [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] for i, j, k { println(list[i][j][k]) } } println(a) }
Current objects
In Dyon, a current object is a variable that can be accessed by name.
To create a current object, use ~
when declaring a variable:
fn main() { ~ list := [1, 2, 3] print_list() } // Get the current object with name `list`. fn print_list() ~ list { for i { println(list[i]) } }
A new current object overrides the old one until it runs out of scope:
fn main() { ~ a := 2 foo() // prints `2` { ~ a := 3 foo() // prints `3` } foo() // prints `2` } fn foo() ~ a { println(a) }
Current objects are useful to store things like settings or assets.
Note to programmers accustomed to globals
Current objects replaces cases where you would use globals in another language. Dyon does not support globals, because dynamic modules should not initialize memory upon loading. A current object is like a global, except you must use it explicitly, and it has a scope which can replace other current objects. You might find it much easier to read code using current objects.
Types
In Dyon, there are two kinds of types:
- Runtime types, which only has one level
- Static types, which can have more than one level
Runtime types
Dyon check the runtime type when using =
to change a variable.
To get the runtime type, use the typeof
function:
fn main() { println(typeof(1.2)) // prints `number` println(typeof(true)) // prints `boolean` println(typeof("hi!")) // prints `string` println(typeof((1, 2))) // prints `vec4` println(typeof([1, 2, 3])) // prints `array` println(typeof({a: 2})) // prints `object` println(typeof(link { 1 2 3 })) // prints `link` println(typeof(some(2))) // prints `option` println(typeof(ok(2))) // prints `result` println(typeof(go foo())) // prints `thread` println(typeof(\(x) = x + 1)) // prints `closure` println(typeof(unwrap(load("src/main.dyon")))) // prints `rust_object` } fn foo() -> { return ok(2) }
Static types
Function arguments can have specify a static type:
#![allow(unused)] fn main() { fn foo(a: f64) { ... } }
By default this is any
.
Static types are used by the type checker to check for errors:
any
, any typef64
, numberbool
, booleanstr
, stringvec4
, 4D vector[]
, array, defaults to[any]
{}
, objectlink
, linkopt
, defaults toopt[any]
res
, defaults tores[any]
thr
, defaults tothr[any]
- Closure type, e.g.
\(any, ..) -> any
- Ad-hoc type, e.g.
Foo
, defaults toFoo {}
There is no static type for Rust objects. Use an ad-hoc type, e.g. Foo any
.
Ad-hoc types
In Dyon, an ad-hoc type is a type that you just give a name. It is not declared anywhere, but used directly when writing the functions. Dyon treats ad-hoc types as distinct from each other.
fn new_person(first_name: str, last_name: str) -> Person { return { first_name: clone(first_name), last_name: clone(last_name) } } fn say_hello(person: Person) { println("Hi " + person.first_name + "!") } fn main() { homer := new_person("Homer", "Simpson") say_hello(homer) // prints `Hi Homer!` }
You can use any name that is not used by other Dyon types. It is common to use CamelCase.
Person
is the same as Person {}
, where {}
is the inner type.
The inner type goes with the ad-hoc type and vice versa:
fn say_hello(person: Person) { println("Hi " + person.first_name + "!") } fn main() { say_hello({first_name: "Homer"}) // prints `Hi Homer!` }
Addition and multiplication
Two ad-hoc types can be added if the inner type allows addition.
Dyon complains if you try to add an ad-hoc type with an inner type.
For example:
#![allow(unused)] fn main() { // Convert number to `km` fn km(v: f64) -> km f64 { return v } km(3) + km(4) // OK km(3) + 4 // ERROR }
Multiplication is not allowed, because this often changes physical units.
Complain when wrong
In Dyon, the type checker only complains when it knows something is wrong.
When you do not specify the type of an argument, it defaults to any
:
foo(x) = x + 1 fn main() { println(foo(false)) }
In the program above, the type checker does not know that something is wrong.
You get a runtime error:
--- ERROR ---
main (source/test.dyon)
foo (source/test.dyon)
Invalid type for binary operator `"+"`, expected numbers, vec4s, bools or strings
1,10: foo(x) = x + 1
1,10: ^
To get a type error, add the type to the argument:
#![allow(unused)] fn main() { foo(x: f64) = x + 1 }
Now the type checker complains, because it knows that false
is not f64
:
--- ERROR ---
In `source/test.dyon`:
Type mismatch (#100):
Expected `f64`, found `bool`
4,17: println(foo(false))
4,17: ^
Static types are not guaranteed
The type of an argument is not guaranteed to be the given static type.
For example:
foo(x: f64) = x + 1 forget_type(x: any) = clone(x) fn main() { println(foo(forget_type(false))) // ERROR }
This gives you a runtime error, because the type checker is not that smart.
In-types
In Dyon, an in-type receives input data from a function.
The in
keyword creates a receiver channel:
#![allow(unused)] fn main() { x := in log }
Next time you call log
, the input data is sent to x
:
fn log(a: f64) {} fn main() { x := in log log(1) println(next(x)) // Prints `some([1])` }
Calling functions
The type is in[[]]
since arguments are stored in array:
fn log(a: f64) {} fn foo(x: in[[f64]]) {println(next(x))} fn main() { x := in log log(1) foo(x) }
Working With Threads
In-types works across threads:
fn log(a: f64) {} fn finish() {} fn bar(id: f64, n: f64) -> bool { for i n { log((id + 1) * 1000 + i) sleep(0.1) } finish() return true } fn main() { log := in log finish := in finish n := 10 for i n {_ := go bar(i, 2)} loop { for msg in log {println(msg[0])} if n == 0 {break} for done in finish {n -= 1} } }
Notice that n == 0
is checked after emptying the log for messages.
Other functions for in-types
fn next(channel: in) -> opt[any]
- Blocks thread until message is received from channel.fn try_next(channel: in) -> opt[any]
- Checks for message on channel.
Lifetimes
In Dyon, a lifetime tells whether an argument outlives another argument.
Lifetimes are rare
In normal programming there is little need to worry about lifetimes.
For example, use clone
when putting a variable inside another:
#![allow(unused)] fn main() { fn put(a, mut b) { b[0] = clone(a) } }
Instead of:
#![allow(unused)] fn main() { fn put(a: 'b, mut b) { b[0] = a } }
It is useful to know how lifetimes work, but you rarely need them in practice.
Lifetimes replaces garbage collector
Some languages can have a pointer to a variable that does not exist. When this happens, it is called "dangling pointer". This can lead to unpredictable behavior and system crashes. Languages that allow dangling pointer are unsafe, and the programmer must be extra careful.
Many languages use a garbage collector to avoid dangling pointers. Instead of removing the variable that the pointer points to, it keeps it around in memory until all its pointers are gone.
Dyon uses static lifetime checks to ensure that dangling pointers are impossible. This removes the need for a garbage collector.
Lifetimes tell the order of declaration
A lifetime tells whether an argument outlives another argument:
// `a` outlives `b` fn put(a: 'b, mut b) { b[0] = a } fn main() { a := [2, 3] // - lifetime of `a` // | b := [[]] // | - lifetime of `b` // | | put(a, mut b) // | | }
The variable "a" outlives "b" because it is declared before "b".
The same program can be written like this:
fn main() { a := [2, 3] b := [[]] b[0] = a }
Here, the order of the declared variables is known.
The return
lifetime
The return
lifetime is the lifetime of the return
variable.
This outlives the default lifetime of arguments (no lifetime).
If you return one of the argument, you must use 'return
or clone
:
#![allow(unused)] fn main() { // With `'return` lifetime. id(x: 'return) = x // With `clone`. id(x) = clone(x) }
The lifetime checker does not understand types
In Dyon, the static type is not guaranteed at runtime,
therefore bool
, f64
and str
follows same rules as []
or {}
:
#![allow(unused)] fn main() { fn foo(a: f64) -> { return a // ERROR } }
--- ERROR ---
In `source/test.dyon`:
Requires `a: 'return`
2,12: return a // ERROR
2,12: ^
Lifetimes are about references
A lifetime is about the references stored inside a variable. All references outlive variables they are stored in. Variables can not store references to themselves, because it can not outlive itself.
In order to put a reference inside a variable, the lifetime checker must know that the reference outlives the variable.
Because of the lifetime checker, all memory in Dyon is an acyclic graph.
Error handling
In Dyon, error handling uses the ?
operator to propagate errors.
fn try_divide(a: f64, b: f64) -> res[f64] { return if b == 0 { err("Division by zero") } else { ok(a / b) } } fn foo(b: f64) -> res { a := try_divide(5, b)? return ok(a + 2) } fn main() { println(foo(2)) // prints `ok(4.5)` println(foo(0)) // prints `err("Division by zero")` }
When using unwrap
, an error is reported with a trace of all ?
operations:
#![allow(unused)] fn main() { println(unwrap(foo(0))) }
--- ERROR ---
main (source/test.dyon)
unwrap
Division by zero
In function `foo` (source/test.dyon)
7,10: a := try_divide(5, b)?
7,10: ^
15,20: println(unwrap(foo(0)))
15,20: ^
An opt
is converted into res
when using ?
,
with the error "Expected some(_)
, found `none()".
Copy-on-write
In Dyon, arrays and objects uses atomic reference counting (Arc).
Whenever there are two non-stack references to the same array, it will create a copy when changing the array:
fn main() { // `[1, 2]` is inside an array, // and is therefore not stored in stack memory. a := [[1, 2]] // Create another reference to `[1, 2]` b := [a[0]] b[0][0] = 3 println(b[0][0]) // The old `[1, 2]` array remains unchanged. println(a[0][0]) }
This means that methods, the building-block of OOP patterns, are impossible to use in Dyon:
fn foo(mut a) { a[0] = 3 } fn main() { // `[1, 2]` is inside an array, // and is therefore not stored in stack memory. a := [[1, 2]] // When we pass it as argument, // the reference counter is increased. foo(mut a[0]) // The value remains unchanged, // because `foo` changed a copy. println(a[0][0]) // prints `1` }
To modify variables inside other variables, it must be declared on the stack:
fn foo(mut a) { a[0] = 3 } fn main() { // `[1, 2]` is put on the stack. list := [1, 2] // Putting a stack reference to `list` inside `a` a := [list] // When we pass it as argument, // the stack reference is passed. foo(mut a[0]) // The value is changed. println(a[0][0]) // prints `3` }
Data oriented design
When writing programs in Dyon, you need to avoid copy-on-write. The easiest way to do this is to organize application structure in flat arrays.
- Do not worry about having lots of arrays
- Use current objects to pass arrays around
- Batch changes to arrays together by iterating over them
Optimize the data structure for the algorithms that process it. When the algorithm changes, the data structure changes too!
Dyon is a scripting language
If you need OOP patterns, then there is only one solution: Use another language, for example Rust. Dyon is designed for scripting, as in solving problems fast. It is not designed for building abstractions, or proving things through the type system.
Dynamic modules
In Dyon, you organize code by using dynamic modules. It is common to write a loader script that loads modules and imports them to other modules.
Dynmod example
Source: examples/dynmod
main.dyon:
fn main() { math := unwrap(load("src/math.dyon")) game := unwrap(load(source: "src/game.dyon", imports: [math])) call(game, "main", []) }
math.dyon:
#![allow(unused)] fn main() { add(a: f64, b: f64) = a + b }
game.dyon:
fn main() { a := 2 b := 3 println(add(a, b)) // prints `5` }
Calling functions in dynamic modules
Dyon peforms a runtime lifetime check of arguments for these functions:
fn call(module, name, args)
- returns no valuefn call_ret(module, name, args)
- returns value
Why dynamic modules?
Module loading is often an important part of a program:
- Download updates
- Refresh game logic for interactive programming
- Swap between backends
For example, the same Dyon game can run on game engines that supports multiple backend APIs for rendering. In other languages you might write generic code to make it reusable across APIs. Dyon solves this by making all code reusable across APIs, as long as the dependencies implement the same set of functions.
Look up functions
In Dyon, you get all available functions with functions
:
fn main() { println(functions()) }
You can write a program that looks up the information about a function:
fn main() { fs := functions() has_foo := any i { fs[i].name == "foo" } if has_foo { why := why(has_foo) println(fs[why[0]]) } } fn foo() {}
{returns: "void", type: "loaded", arguments: [], name: "foo"}
Because Dyon uses dynamic modules, functions
is the only standard way
of obtaining this information. This can only be known in the loaded module.
Optimize performance
To optimize the performance of Dyon programs, compile with cargo build --release
.
You can also get further optimization by disabling the default debug_resolve
feature.
This compares location of variables on the stack with the static location.
It is not needed for running programs, but used to detect bugs in Dyon.
To disable debug_resolve
, change the "Cargo.toml":
[dependencies.dyon]
version = "0.8.0"
features = []
Interop with Rust
Dyon is designed to work with Rust, but uses different types.
Rust uses static types only, which usually takes up less memory and runs faster. When you need to high performance, it is recommended to use Rust.
Functions example
Source: dyon/examples/functions.rs
The dyon_fn!
macro lets you write a Rust function that maps to Dyon types:
#![allow(unused)] fn main() { #[macro_use] extern crate dyon; dyon_fn!{fn say_hello() { println!("hi!"); }} }
Add the function to a module:
#![allow(unused)] fn main() { module.add(Arc::new("say_hello".into()), say_hello, Dfn { lts: vec![], tys: vec![], ret: Type::Void }); }
The dyon_obj!
macro lets to map new Rust structs to Dyon objects:
#![allow(unused)] fn main() { pub struct Person { pub first_name: String, pub last_name: String, pub age: u32, } dyon_obj!{Person { first_name, last_name, age }} }
All fields must implement embed::PopVariable
and embed::PushVariable
.
The Dfn
struct
The Dfn
struct describes the type information of the function signature:
lts
- lifetimes, e.g.Lt::Default
tys
- types, e.g.Type::F64
ret
- return type, e.g.Type::Bool
This is used by the lifetime and type checker when loading modules.
lib.dyon
When designing Dyon libraries written in Rust, it is common to include a "lib.dyon" file in the "src" folder. This file lists all external functions as they would appear in Dyon code.
Two examples from the Dyon library:
#![allow(unused)] fn main() { /// Returns an array of derived information for the truth value of `var`. /// This can be used with the value of `∃`/`any` and `∀`/`all` loops. fn why(var: bool) -> [any] { ... } /// Returns an array of derived information for the value of `var`. /// This can be used with the value of `min` and `max` loops. fn where(var: f64) -> [any] { ... } }
Comments should start with ///
.
Mutability interop
Mutability information is part of the function name:
#![allow(unused)] fn main() { fn foo(mut a: f64, b: f64) { ... } }
The name of this function is foo(mut,_)
.
#![allow(unused)] fn main() { module.add(Arc::new("foo(mut,_)".into()), foo, Dfn { lts: vec![Lt::Default; 2], tys: vec![Type::F64; 2], ret: Type::Void }); }
Lifetime interop
The Lt
enum describes the lifetime of an argument.
The most common lifetime is Lt::Default
(no lifetime).
If you have a function like this:
#![allow(unused)] fn main() { fn foo(mut a: [{}], b: 'a {}) { ... } }
Then the Dfn
struct looks like this:
#![allow(unused)] fn main() { Dfn { lts: [Lt::Default, Lt::Arg(0)], tys: [Type::Array(Box::new(Type::Object)), Type::Object], ret: Type::Void } }
Lt::Arg(0)
means the argument outlives the first argument.
Lifetimes must not be cyclic. For example, this is not valid Dyon code:
#![allow(unused)] fn main() { fn foo(a: 'b, b: 'a) { ... } }
Lt::Return
means the arugment outlives the return value:
#![allow(unused)] fn main() { fn foo(a: 'return) -> { ... } }
Manual interface
A manual interface gives more control over the interface between Rust and Dyon. This is the only way to write external functions that mutate arguments.
The signature of an external function:
#![allow(unused)] fn main() { fn(rt: &mut Runtime) -> Result<(), String> }
These functions are useful when pushing and popping variables:
Runtime::pop
- convert from stackRuntime::pop_vec4
- convert 4D vector from stackRuntime::pop_mat4
- convert 4D matrix from stackRuntime::var
- convert from variableRuntime::var_vec4
- convert 4D vector from variableRuntime::push
- convert to variable on stackRuntime::push_vec4
- convert to 4D vector on stackRuntime::push_mat4
- convert to 4D matrix on stackRuntime::resolve
- resolve a variable reference
Getting arguments of function
Since the Dyon runtime uses a stack, you must pop the argument in reverse order.
Before you use an argument, you must use Runtime::resolve
in case
it is a reference to a variable:
#![allow(unused)] fn main() { let draw_list = rt.stack.pop().expect("There is no value on the stack"); let arr = rt.resolve(&draw_list); }
Runtime::pop
, Runtime::pop_var
, Runtime::pop_vec4
, Runtime::pop_mat4
, Runtime::var
, Runtime::var_vec4
and Runtime::var_mat4
resolves the variable for you.
Mutate argument
To mutate an argument, you need to obtain a mutable reference to the resolved variable on the stack:
#![allow(unused)] fn main() { let v = rt.stack.pop().expect(TINVOTS); if let Variable::Ref(ind) = v { let ok = if let Variable::Array(ref mut arr) = rt.stack[ind] { Arc::make_mut(arr)...; } } }
Return value
After popping argument and computing a value, push the result on the stack. Do not push more than one value, since Dyon only supports a single return value.
Reading from a Dyon variable
The Runtime::var
function converts a value inside a variable:
#![allow(unused)] fn main() { let radius: f64 = rt.var(&it[2])?; }
4D vectors
The Runtime::var_vec4
function converts to a vec4
convertible type:
#![allow(unused)] fn main() { let color: [f32; 4] = rt.var_vec4(&it[1])?; }
4D matrices
The Runtime::var_mat4
function converts to a mat4
convertiable type:
#![allow(unused)] fn main() { let mat: [[f32; 4]; 4] = rt.var_mat4(&it[1])?; }
Piston-Current
Dyon keeps no track of variables in the Rust environment. You can use the Piston-Current crate to read from or change such variables by type.
#![allow(unused)] fn main() { pub fn render(rt: &mut Runtime) -> Result<(), String> { rt.push(unsafe { Current::<Option<Event>>::new() .as_ref().expect(NO_EVENT).render_args().is_some() }); Ok(()) } }
Standard Library
Dyon organizes functions in 3 ways:
- Dyon modules (also called "loaded functions")
- Rust interop (also called "external functions")
- Standard Library (also called "intrinsics")
- and 2) are explained in previous chapters.
This chapter is about the third point 3) Standard Library.
This is what the Standard Library contains:
- Functions for loading modules and calling functions
- Functions for using Dyon's built-in types
- Functions for loading and saving data
- Functions for meta-parsing
The standard library of Dyon is small compared to e.g. Rust (less than 150 functions). If you want to know the details of how it works, you can read the source code here.
Some functions are in the standard library because they use special knowledge that is not available in external functions.
This chapter shows some things you can do with the Standard Library.
Command-line arguments
Dyon has a function to return an array of command-line arguments:
#![allow(unused)] fn main() { fn print_args_list { println(args_os()) } }
The first item is the path of the executable.