Yao Tutorial

Rig’s language is called Yao. It a full general-purpose programming language.

The basic syntax looks like this:

nums: []num = @[
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
];

foreach n: nums
{
	out: str =
	if n % 3 == 0
	{
		"Fizz" +~ if n % 5 == 0 { "Buzz"; } else { ""; };
	}
	else if n % 5 == 0
	{
		"Buzz";
	}
	else
	{
		str(n);
	};

	print(out +~ "\n");
}

Booleans

Yao has boolean types, that look like this:

b: bool = false;
c: bool = true;

The keywords true and false are the boolean literals.

Numbers

Numbers are arbitrary-precision by default, and they can be written just like any other programming language:

n1 := 1;
n2 := 0x5566_4433;
n3 := 0b1011_0010;

Underscores can separate sections of numbers.

Variables

Variables are declared in two ways:

n1 := 4;
n2: num = 5;

In the first way, the type of the variable is inferred from the type of the expression being assigned to it.

In the second, the type of the expression must match the explicit type.

Strings

Strings are surrounded by double quotes:

s1 := "string";

Types

Yao is strongly-typed with static type checking.

Types include a name plus a stack of modifiers that go from right to left before the type name:

// n1 is a *mutable* number.
n1: !num = 1;

// n2 is an array of numbers.
n2: []num = @[ 1, 2, 3 ];

// n3 is an *array of* *mutable* numbers.
n3: []!num = @[ 4, 5, 6 ];

// n4 is a *mutable* *array of* numbers.
n4: ![]num = @[ 7, 8, 9 ];

// n5 is an owned *pointer* to a number.
n5: ^num = 5;

// n6 is a non-owned *reference* to a number.
n6: &num = &n1;

Why are the modifiers reversed and before the type name? Because it means that in general, when reading Yao types, reading modifiers from left to right and then reading the type name will be correct English formulation of the actual type.

Array Literals

Array literals are created by using @[ for the beginning of the array and ] for the end:

// A trailing comma is fine, but not required.
nums: []num = @[ 1, 2, 3, ];

Concatenation

You can concatenate strings and arrays with the +~ concatentation operator:

s1 := "Hello, ";
s2 := "World!";

// s3 is "Hello, World!"
s3 := s1 +~ s2;

a1 := @[ 1, 2, 3 ];
a2 := @[ 4, 5, 6 ];

// a3 is @[ 1, 2, 3, 4, 5, 6 ].
a3 := a1 +~ a2;

Expressions vs. Statements

Yao is an expression-oriented language with statements.

That is a small contradiction, so this may be the hardest part to understand about Yao.

Unless you are Rust user, in which case just remember one small difference: expressions returned from blocks still need semicolons.

Yao is a procedural language, which means it executes one unit of code at a time. These units are called statements.

Statements can be either simple or compound.

A simple statement is roughly what you would expect on one line of code. A compound statement is one that can nest other statements inside of itself.

For example, in this code:

{
	// Pretend y and z exist.
	x: num = y + z;
	w: num = x * 4;
}

the braces form a compound statement called a block.

Some statements don’t return a value; they include assignments, calls to functions that don’t return a value, and blocks that don’t return a value, among other things.

Some statements do return a value; they include calls to functions that return a value, any math or other value that is not assigned to a variable, and blocks that return values.

Yes, blocks can return values:

v: num =
{
	// Pretend y and z exist.
	x: num = y + z;
	w: num = x * 4;

	// Returns w as the result of the block, which will be assigned to v.
	w;
};

if Statments/Expressions

Some keywords can act like a statement, some can act like an expression, and some can act like both.

if is an example of a keyword that can act like both.

When acting like a statement, it acts like if in all other programming languages.

When acting like an expression, it selects the value to return based on the condition(s):

a: num =
if cond
{
	// If cond is true, 5 is assigned to a.
	5;
}
else
{
	// If cond is false, this whole branch is executed.
	x: num = factorial(5);

	// 5! + 6 is assigned to a.
	x + 6;
};

There are two things to note about the code above:

  1. There are no parentheses required around the condition, and
  2. There is a semicolon (;) after the whole if statement!

It is a syntax error if that semicolon does not exist when an if is used as a subexpression, but it is not a syntax error if the if stands alone:

a: num =
{
	if cond
	{
		// If cond is true, 5 is assigned to a.
		5;
	}
	else
	{
		// If cond is false, this whole branch is executed.
		x: num = factorial(5);

		// 5! + 6 is assigned to a.
		x + 6;
	}
};

Note that the block now has a trailing semicolon.

If an if has more than one branch, all branches must return a value of the same type, or all must not return a value.

If a branch does not have an else branch, it will return the zero value of the type returned by the other branch(es).

Loop Statements/Expressions

Loops can also be statements or expressions.

The value returned by an loop expression is an array of all of the values returned by the body of the loop, one expression for each iteration:

nums: @[ 1, 2, 3, 4, 5 ];

// v will be equal to @[ 1, 2, 6, 24, 720 ];
v: []num =
foreach n: nums
{
	factorial(n);
};

The foreach will loop over every item in the list after the colon in the header, assigning that item’s value to the variable name given before the colon in the header.

The foreach can return nothing:

foreach n: nums
{
	print(n);
}

The while loop iterates until the given condition is false:

cond: !bool = get_cond();

while cond
{
	print("Yao");

	// To mutate a variable, the `!` character must come after the variable.
	cond! = get_cond();
}

Like if, no parentheses are necessary around the condition, and like foreach it can act as a statement or an expression.

Functions

Functions are defined with the fn keyword, and their parameters use the typical name: type syntax. The return value comes after a right arrow (->) item.

It looks like this:

// A trailing comma is fine, but not required.
fn factorial(x: num) -> num
{
	// Make sure the number is positive.
	n: num = if x < 0 { -x; } else { x; };

	// Base case.
	if n == 1
	{
		return n;
	}

	// The return keyword is unnecessary because the function will return
	// the result of the last statement/expression in its body.
	return n * factorial(n - 1);
}

To have a function return nothing, use the name void after the arrow.

To call a function, just put the function name, followed by a left parenthesis, the arguments, then a right parenthesis:

f: num = factorial(100);