4. Statements
Like previously mentioned, LiteScript simplifies the statement model into a fixed-form model that allows for its parsing and evaluation to be simple and straight forward, so it is important to be aware of what can and cannot be done.
4.1 Assigners and Expressions
Just like assembly, for assignment statements where we either declare a variable or reassign its value, we derive the assembly movement instruction into the following fixed form:
[variable] [assigner] [expression]
A variable is declared when it is first mentioned in an assignment statement, and recurring mentions simply just reassign its value.
First declaredx [assigner] expression1Value reassignedx [assigner] expression2
When it comes to value reassignments, since LiteScript is a dynamically typed programming language, a variable's type can be changed during runtime from one type to another. So if expression1 evaluates to an integer, x is declared as an integer type, and if expression2 evaluates to a string, then x has its value reassigned and its type changed to a string.
In LiteScript there are only three basic assigners:
-
Equality assigner (=): Simply sets the variable's value and type to whatever the expression evaluates to. Compatible with every primitive data type and containers.
-
Incremental assigner (+=): Equivalent to x = x + expression. Evaluates the expression, and performs the addition of the expression's corresponding type with the same variable, therefore, the variable needs to have been declared prior to this. Compatible with every primitive data.
-
Decremental assigner (-=): Equivalent to x = x - expression. Evaluates the expression, and performs the subtraction of the expression's corresponding type with the same variable, therefore, the variable needs to have been declared prior to this. Compatible only with integers and reals.
4.1.1 Expressions and Operators
In LiteScript, expressions are evaluated rather unconventionally. Instead of evaluating a parsed AST, the expression instead gets converted to postfix and evaluated using the Shunting Yard Algorithm, so any rules and evaluation orders applied to these will be applied to every LiteScript expression.
Unlike C, there is no lazy evaluation, so expressions that are supposed to represent boolean values are evaluated in its entirety.
In the following table, we describe every operator that can be used in an expression, including what it evaluates (expressed either in C like pseudocode, or text), the precedence value (lower values indicate lesser precedence) and if it is unary, binary or both; for every primitive data type and ordered by ascending precedence.
| Operator | Symbol | Precedence | Unary/Binary | Integer | Real | String | Evaluates to |
|---|---|---|---|---|---|---|---|
| Logical OR | || | 1 | Binary | (a != 0 || b != 0) ? 1 : 0 | - | - | Integer |
| Logical AND | && | 2 | Binary | (a != 0 && b != 0) ? 1 : 0 | - | - | Integer |
| Bitwise OR | | | 3 | Binary | a | b | - | - | Integer |
| Bitwise XOR | ^ | 4 | Binary | a ^ b | - | - | Integer |
| Bitwise AND | & | 5 | Binary | a & b | - | - | Integer |
| Equals | == | 6 | Binary | (a == b) ? 1 : 0 | (a == b) ? 1 : 0 | (a == b) ? 1 : 0 | Integer |
| Not equals | != | 6 | Binary | (a != b) ? 1 : 0 | (a != b) ? 1 : 0 | (a != b) ? 1 : 0 | Integer |
| Less than | < | 7 | Binary | (a < b) ? 1 : 0 | (a < b) ? 1 : 0 | (a < b) ? 1 : 0 | Integer |
| Less or equal than | <= | 7 | Binary | (a <= b) ? 1 : 0 | (a <= b) ? 1 : 0 | (a <= b) ? 1 : 0 | Integer |
| Greater than | > | 7 | Binary | (a > b) ? 1 : 0 | (a > b) ? 1 : 0 | (a > b) ? 1 : 0 | Integer |
| Greater or equal than | >= | 7 | Binary | (a >= b) ? 1 : 0 | (a >= b) ? 1 : 0 | (a >= b) ? 1 : 0 | Integer |
| Bitwise Left Shift | << | 8 | Binary | a << b | - | - | Integer |
| Bitwise Right Shift (signed) | >> | 8 | Binary | a >> b | - | - | Integer |
| Addition | + | 9 | Binary | Integer addition | Floating point addition (if at least one is a real) | Concatenation | Integer/Real/String |
| Subtraction | - | 9 | Both | Integer subtraction | Floating point subtraction (if at least one is a real) | - | Integer/Real |
| Multiplication | * | 10 | Binary | Integer multiplication | Floating point multiplication (if at least one is a real) | - | Integer/Real |
| Division | / | 10 | Binary | Integer multiplication | Floating point multiplication (if at least one is a real) | - | Integer/Real |
| Modulus | % | 10 | Binary | Remainder of an integer division | - | - | Integer |
| Exponentiation | ** | 11 | Binary | - | a to the power of b (if at least one is a real) | - | Real |
| Logical NOT | ! | 12 | Unary | (a == 0) ? 1 : 0 | - | - | Integer |
| Logarithmation | ~ | 12 | Unary | - | log base 2 of a | - | Real |
Parentheses can be used to clearly indicate the order in which operations should precede past others defined by the language's precedence table.
For expressions that are meant to represent boolean values, integers by default represent them the best and all logical operators evaluate to integer types, however, reals and strings can represent them too. For reals, 0.0 is false and everything else is true, and for strings, an empty string "" is false and anything else is true.
4.2 Control Flow
The language offers the abstraction of control flow by providing the two basic necessary keywords for conditional and iterative logic, inspired by the control flow statements available in UML.
- The "opt" keyword
opt [expression]...
LiteScript's version of the "if" statement. Just like in assembly, if the expression evaluates to true the program enters the next indented scope, which ends until we return to a prior scope; otherwise skip all the statements of the indented scope until reaching the next statement within the same scope.
The opt keyword is the main method for writing conditional statements, with no abstractions for else-if, else and switch statements, we consider that this is enough and all that is ever going to be needed.
- The "loop" keyword
loop [expression]...
LiteScript's version of the "while" statement. If the expression evaluates to true, then run just like an opt statement, execute the code in the following indented scopes, however when the scope ends, return to the loop statement to reevaluate the expression and see what it now evaluates. If it evaluates, skip the following indented scopes just like the opt statements.
The loop keyword is therefore the sole method for writing iterative statements, only abstracting the jump to label behavior from assembly, with no abstractions for other loops like "for" and foreach loops.
Thus, with these control flow keywords, we guarantee that LiteScript can simulate a Turing machine, therefore proving the language's turing completeness, meaning that the language can perform any theoretical mathematical computation.
- The "conditional" keyword (deprecated)
conditional [expression]...
This used to be the only keyword for conditional statements up until LiteScript 0.8.2, where it was replaced by the opt keyword because it was to verbose and tedious to write continuously. It is still supported for backwards compatibility, but its use is discouraged.
4.3 Miscellaneous Keywords
There are still a couple of keywords missing that provide extra behavior to the language, most notably global variables which is what mainly transforms LiteScript from being a functional programming language to a procedural one.
- The "global" keyword
global [variable]
Since the global scope in LiteScript doesn't exist, we need a way to create variables/symbols that are accessible throughout the entire runtime. So the language offers a keyword that creates a global reference during runtime like Python, but unlike Python this is the only exclusive way to do this.
All it does is bind a symbol/name of a variable of any type, including primitive and containers, (that has to be declared later) to a global reference that can be referenced anytime after its creation by any scope. All following modifications to a global variable will have their changes visible globally at a per line granularity, so after the statement has finished executing. These global variables are also shared between threads, which is the only method of inter-thread communication that is natively supported.
It is important to then declare the variable with a value after declaring the global reference, as its value is completely undefined at initialization and cannot be used.
global xx = 0y = 1 + x ✅
global xy = 1 + x ❌
The global keyword can also be used for making local variables global during runtime, in this case the value will be correctly initialized with the current value it is holding.
x = 3global x
- The "exit" keyword
exit [integer]
LiteScript provides a built-in keyword for exiting the program, with an optional integer parameter that specifies the exit code. If the parameter is not provided, then the program exits with a default code of 0.
When calling exit, all imported modules running and their implicit data structures and objects are automatically cleaned up during exiting, so there is no need for manual cleanup.