Statements

Within a function are multiple statements. The order of the statements defines their dependence on one another based on what they manipulate in their scope hierarchy. The compiler uses this ordering and what variables are required of each statement to determine a dependency graph of operations. This information is provided to the runtime which may reorder statements to improve performance. Currently this is only used for batching independent IO operations together to execute in parallel and combining the CPU-bound operations into fewer segments to reduce iteration through the event loop, but could eventually be used to identify CPU-bound operations that are parallelizable due to independence from one another and are long enough such that the synchronization costs do not outweigh the performance gain from parallelization.

Statements must be on different lines, but statements can span multiple lines. There are six types of statements: variable declarations, variable assignments, function calls, function returns, event emission, and conditionals (if statements).

Variable Declarations

There are two types of variables, constants and mutable variables. Constants are declared with these two syntaxes:

const varname: typename = constantOrFunctionCallOrExpression;
const varname = constantOrFunctionCallOrExpression;

The typename is optional as it can be inferred from the value being assigned. Providing the type can be useful to make sure your code is doing what you expected, though, and not using an unexpected type. By default all numeric constants are automatically the highest definition version possible. (int64 or float64) Explicitly setting a smaller bit size for the constant will not coerce the constant automatically, you must explicitly declare the down coercion, eg:

const someI32: int32 = 5.toInt32();

Any type that exists in the language can be assigned. Simple constants are simply declared, or produced via function calls or operator expressions. User-defined types can be assigned to constants through the use of a constructor function and calling it, through an expression involving operators that produces the user-defined type, or through the user-type literal syntax. Arrays and HashMaps have similar but slightly different syntaxes, as they deal with inherently plural data versus the struct-like user-defined types:

const simpleConstant: bool = true;
const simpleConstant2 = "simple";
const expressionResult: int64 = 3 + 5 * 2 + fnCall2(3, 1) || 0;
const fnResult: someType = functioncall(3, 5);
const userTypeLiteral = new UserType {
  key: "val",
  foo: true,
  bar: 3
};
const arrayTypeLiteral = new Array<int64> [ 1, 1, 2, 3, 5, 8, 13 ];
const otherArrayTypeLiteral = [ "foo", "bar", "baz" ];

All complex type literals begin with the new keyword, followed by the type name, which must be a realized generic if it is a generic type. If the original generic type was an Array it gets a special syntax. Arrays are surrounded by brackets ([, ]) instead of curly braces ({, }) and are simply a comma-delimited list of values (they could also be generated by functions or operator expressions, or be a user-defined type literal). Arrays are also the only special type that can be unambiguously declared without the new Type prefix, so that is also allowed.

For mutable variables, the syntax is nearly identical:

let varname: typename = constantOrFunctionCallOrExpression ;
let varname = constantOrFunctionCallOrExpression;

Where const is replaced with let, making it clear that this variable's value can be changed.

Variable Assignments

This syntax only works with mutable variables:

varname = constantOrFunctionCallOrExpression;
varname.propertyName = constantOrFunctionCallOrExpression;

Since the variable type is already known, it is no longer included. Anything that could be assigned at declaration time is assignable in future assignments. When re-assigning to the properties of user-defined types, you use the dot notation.

Function Calls

Because functions can have side effects that might be desirable, and functions defined within the scope of the current function could potentially mutate multiple variables, and so on, it's allowed to call them without storing a return value. It simply looks like this:

someFunction(arg1, arg2)

where the arguments themselves could be constants, variable names, other function calls, operator statements, and variable type selection.

Function Returns

A return statement sets the value of the return type for the function and ends execution:

return constantOrFunctionCallOrExpression;

If the function has no return value, you can also trigger an exit without assigning a return value by simply stating:

return;

If you have any statements after the return it will not compile, however (except if the return is within a conditional statement, described below).

Event Emission

A function can trigger new events to occur by emitting one:

emit eventName;
emit eventName constantOrFunctionCallOrExpression;

If the event is a void event (no arguments given to the handler functions) then you use the simple first syntax. If there is a value provided to the handler, then as with the other statements it could be a constant or variable name or function call or operator expression or type name of a variable that is provided.

Conditionals

Conditionals optionally run one block of code (or potentially another) based on a condition. Since this language is intended to run statements in dependency order, the scope the conditions run in are actually functions to prevent them from being run ahead of time. This also means all scopes once you enter a function scope are function scopes, which makes things simpler to reason about. If one of these functions returns a value, the outer function immediately returns that value from the conditional statement.

The conditional syntax is:

if booleanConstantOrFunctionCallOrExpression thenFunction
if booleanConstantOrFunctionCallOrExpression thenFunction else elseFunction
if booleanConstantOrFunctionCallOrExpression thenFunction else if ...

where the constant, function call, or operational expression must evaluate to a boolean (so no type checking, unless it is within an equality check against an expected type name).

Conditionals are one of the two places (the other being event handler declarations) where the fn may be omitted from a purely-side-effect function (no arguments, no return value), so the above can look exactly as one would expect:

if 1 == 2 {
  print("Wrong");
} else {
  print("Right");
}

The conditional has two anonymous functions declared inline. But it could just as easily be something like:

fn right() {
  print("Right");
}

fn wrong() {
  print("Wrong");
}

const condition: bool = 1 != 2;

if condition right else wrong