Statements
Statements are separated into two groups: operations and control flow. Operations are assignments and procedure calls. Control flow includes if
, each
and while
statements. Statements can be grouped into statemenet block using do
and end
. Statement block is treated like a single statement.
do
a := a + 1;
b := b + 1;
end;
Each statement inside statement block must be ended with ;
. Statement block without statements inside is empty statement block and does nothing. Also some statements can have their own inner variables. These variables are accessible only inside their statements, but are declared in function scope so their names should not conflict with local variables.
Expressions
Expressions in Jarog represents relations between constants, variables and values, declared using math operations.
Operations
To express relations between variables and constants Jarog has unary and binary operations. Unary operations have the highest priority.
Symbol | Operand types | Description |
---|---|---|
- |
int , float |
Minus |
! |
bool |
Logical Not |
@ |
list , set |
List length or set power |
Binary operations have different priority. Jarog supports next operations given in order by their priority. Higher operations have higher priority.
Symbol | Operand types | Description |
---|---|---|
^ |
int , float |
Power |
* |
int , float , set |
Multiplication |
/ |
int , float , set |
Division |
% |
int , float |
Modulo |
+ |
int , float , set |
Addition |
- |
int , float |
Subtraction |
: |
int |
Range |
= |
any type | Equals |
<> |
any type | Not equals |
> |
int , float , set |
Is greater |
< |
int , float , set |
Is less |
>= |
int , float |
Is greater or equal |
<= |
int , float |
Is less or equal |
& |
bool |
Logical And |
| |
bool |
Logical Or |
- Length operation (
@
) is applicable to lists or sets, it returns length of list or set power (number of elements in set). - Power, multiplication, modulo, addition and subtraction keeps result
int
if both operands areint
. - Division always results
float
. - Range is a special operation, it constructs list of integers from first value to last with step 1, both values are included. If first value is bigger than last then list is reversed.
Procedure call
Procedures are functions, that return no results, procedures unlike functions can be called instantly.
const proc := $() do
# do smth here
end;
...
proc();
Functions and any other expressions can not be called instantly cause they all have results, which must be assigned to some variable (or logged). On the other hand procedures can not be used in expressions or logged.
log proc(); # will cause an error
Assignment
Assignment calculates expression result and assigns it to a variable. Unlike the most of languages Jarog uses symbol :=
to assign values.
<var1> := <expr1>;
Also there is a way to assign multiple values at once.
var a, b, c: int;
...
{a, b} := {0, 1};
{a, b} := {b, a}; # swap values
[a, b] := [0, 1]; # lists are fine too
[{a, b}, c] := [{1, 2}, 3] # even combined values
Structures and lists are automatically destructed into tuple of variables and tuple of values can be composed into a struct and assigned to it as a single value. For better understanding see Structs and Lists
If statement
If statement has next syntax structure:
if (<expression>)
<statement1>
else
<statement2>;
Expression must be bool, first branch is executed if expression results true otherwise second branch is executed.
Second branch is optional.
if (<expression>) do
<statement1>;
<statement2>;
...
end;
While statement
While statement is the generic way to declare loops in Jarog. Expression must return boolean. Loop body is repeated while expression is true.
while (<expression>)
<statement1>;
Break and Continue
Like the most other languages, Jarog has break
and continue
statements, used to interrupt loop execution. break
stops loop execution and continue
stops current iteration of the loop and moves to next one.
while (true) do
n := n + 1;
if (n > 10)
break;
end;
Each statement
Each statement is a special loop construction designed to walk through list or set. Example of declaration is next:
each (<list> -> <var>: <type>)
<statement>;
It accepts two arguments - <list>
and <var>
. <var>
is inner variable, it is declared and accessible only inside each statements. <type>
must be compatible with <list>
item type. Loop is executed as many times as many items <list>
contains. Each time next item of list is assigned to <var>
. <var>
is writable inside loop but this doesn’t affects <list>
items. Also list items can be changed during loop execution but it wouldn’t take any effect on loop execution as it uses a copy of <list>
during all iterations. Loop execution can be interrupted with break
and continue
statements. After loop ends item
preserves it’s value assigned to it during the last iteration. Example:
each([1, 2, 3] -> n: int)
log n;
There is alternative form of each
when no variable is declared.
each (<list> -> <expression>)
<statement>;
<expression>
must be assignable to use it inside each. In this case after the last iteration last value of expression is preserved outside of each
. For example this code will print 1 is one, 2 is two, 3 is three,
:
var n: int;
var s: string;
...
each([{1, "one"}, {2, "two"}, {3, "three"}] -> {n, s})
io::put(str::fmt("%d is %s, ", [n, s]));
With statement
With statement syntax is displayed below:
with (<expression> -> <var>: <type>)
<statement>;
This statement declartes a variable visible in next statement only similar to each
statement. See more in statement variables
Exception handling
Exceptions can be handled with next syntax construction:
try
<statement>
catch(<var>: <type>)
<catch statement>;
Variable <var>
shouldn’t be declared in function scope as it is inner variable. There is no special type Exception to be thrown in Jarog. Value of any type can be thrown. Standard exceptions throw string.
Here is some examples for better understanding.
var f: float;
try
f := f / 0 # division by zero throws string
catch(e: string) # which can be caught here
io::print(e);
There is also an ability to use try with multiple catches and throw own exceptions.
try
throw 5 # int exception is thrown
catch(e1: string)
io::print(e1) # this catch will be ignored
catch(e2: int) do
if (e2 = 5)
io::put("Error 5")
else
io::put("Unknown error");
end;
When the exception is thrown interpreter searches first catch clause, which has compatible type and executes it. If no suitable catch clauses found, exception is thrown again. Also if there is two or more clauses with compatible types, only first one is executed. Never move catch with int type below catch with float, otherwise float will eat all int exceptions.
try
throw 5 # int exception is thrown
catch(e1: float) # int can be assigned to float
io::put("float error") # so this will be called
catch(e2: int)
io::put("int error"); # is never called
Catch section can has no variables at all. In this case it will catch all exceptions, but it will have no information about it.
try
throw "I'm exception!"
catch
io::put("Some terrible exception happened but we don't know which :(");
Exception handling does not support finally section. Instead of this Jarog has binded functions.
Try Finally
Instead of catch try
statement can have finally
section. This statement is executed always, even if exception is thrown inside or return statement is called. It does not eats exception so it can be used inside try-catch statement.
Log
Log prints expression value to stdout. It can be used for debug purposes. It accepts expressions with any result types. Expression is executed and it’s result is printed to console.
log "Hello, world!";
Run
Run accepts procedure. It calls the procedure and runs in a new thread. When the procedure is executed thread is terminated.
const proc := $() do
# do smth here
end;
run proc();
Threads can interact with other threads and share data among them using global variables, closures or pipes, but accessing a global variable or closure without synchronization can cause undefined behavior and data corruption.
Sync
Sync statements wraps an instruction into critical section and ensures that only one thread at a time can execute it. Sync can have a name - string variable. If there are few blocks with the same name, only one of them can be executed at a time. Name is optional.
sync("<name>") do
<statement>;
...
end;
Sync blocks help to prevent data corruption on multiple thread access to a single variable.