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:
- There are no parentheses required around the condition, and
- There is a semicolon (
;
) after the wholeif
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);