Expressions
- Operands
- Qualified identifiers
- Function literals
- Primary, secondary and tertiary expressions
- Assignment expressions
- Member expressions
- Index expressions
- Slice expressions
- Calls
- Instantiations
- Operators
- Arithmetic operators
- Comparison operators
- Logical operators
- Casting
- Self expressions
- Super expressions
- InstanceOf expressions
- New expressions
- Drop expressions
- Arguments
- Ternary expressions
- Constant expressions
- Expression precedence
- Order of evaluation
TODO: fit ?? and ??= into all of this… |
Operands
Operand form the basic values of an expression. This includes literal, a (possibly qualified) identifier denoting a constant, variable or function, or a parenthesized expression.
Operand = Literal | OperandName [ TypeArgs ] | "(" Expression ")" ; Literal = BasicLit | CompositeLit | FunctionLit ; BasicLit = int_lit | float_lit | char_lit | string_lit ; OperandName = identifier | QualifiedIdent ;
A operand name refering to an generic function may be followed by a list of type arguments to result into an instantiated function, which makes the function callable.
Qualified identifiers
A qualified identifier is a expression that contains the path to a nested identifier, such as a namespaced one.
QualifiedIdent = identifier { "::" identifier } ;
namespace Test namespace Inner var int abc; end end # This is a qualified identifier for # the 'abc' identifier in # the 'Test::Inner' namespace Test::Inner::abc
Function literals
TODO: tinker with this |
Primary, secondary and tertiary expressions
PrimaryExpr = Operand | SelfExpr | NewExpr || DropExpr | SuperExpr | CastExpr | ArgumentsExpr ; SecondaryExpr = PrimaryExpr | MemberExpr | CallExpr | IndexExpr ; TertiaryExpr = SecondaryExpr | InstanceOfExpr ;
Assignment expressions
Assignments replace the current value stored in a variable, property or field with a new value specified by an expression. For this, the left expression needs to be assignable.
AssignExpr = Expression assign_op Expression ; assign_op = [ add_op | mul_op ] "=" ;
Member expressions
A member expression is a expression which specifies a member of an composite type. The result depents on the member kind being part of the expression.
MemberExpr = (SecondaryExpr | SuperExpr) "." identifier ;
The left hand side must be a expression which value will have a composite type, and the identifier must be declared inside the composite type as a normal, non-static member. |
If the expression resolves to:
A field, the expression is assignable and allows read & writing to the field.
A method, the expression’s type is a function-type of said method and is therefor callable. The value will be a method reference, which will rember the instance used to create it.
A property will result in an newly constructed callable, which provides two overloads for the call operator if the correspoding getter / setter is declared on the property:
One with no arguments and a result type of the property which can be used to call the getter.
A second one with one argument with the type of the property and a void result type which can be used to call the setter.
Index expressions
A index expression is when the index operator []
is used onto a expression that’s indexable. It uses one (or more) keys and results into a single value.
IndexExpr = SecondaryExpr "[" IndexKeyList [ "," ] "]" ; IndexKeyList = Expression { "," Expression } ;
One example would be to retrieve the value of an array or a map:
var carray[int, 2] a = [ 11, 22 ]; # this expression would be of type int, # and would return the value '11' a[0]
Slice expressions
TODO: tinker with this first |
Calls
Calls are expressions that call a function or any expression which type is callable.
CallExpr = SecondaryExpr "(" ArgumentList [ "," ] ")" ; ArgumentList = Expression { "," Expression } ;
If the expression to call denotes a generic function, it needs to be instantiated first.
Passing arguments to ...
parameters
When calling a variadic function, lapyst groups the arguments greedy from left to right, but makes sure any non-variadic non-default parameter is getting an argument as well.
def void myFunc( int... numbers, int... other_nums ) end myFunc(1, 2, 3, 4) def void otherFunc( int... numbers, int i ) end otherFunc(1, 2, 3, 4)
This is called with 1, 2, 3, 4 being put into numbers , while nothing is given to other_nums . | |
Here 1, 2, 3, 4 is given to numbers while 4 is assigned to i . |
Instantiations
Any generic function or type needs to be instantiated before it can be fully used. Instantiation is the process of substituting type arguments for the type parameters; this is done in two steps:
For every type parameter, it’s type argument is used as a substitution in the generic declaration; this includes the type parameter list itself.
Secondly every type argument needs to satisfy the constraint of the type parameter it is used for. If this is not the case, the instantiation will fail.
When using a generic function, type arguments may be inferred from the context where the function is used in.
TODO: elaborate more + explain type inference |
Operators
Expression = UnaryExpr | Expression binary_op Expression ; UnaryExpr = PrimaryExpr | unary_op UnaryExpr ; binary_op = "||" | "&&" | rel_op | add_op | mul_op ; rel_op = "==" | "===" | "!=" | "!==" | "<" | "<=" | ">" | ">=" ; add_op = "+" | "-" | "|" | "^" ; mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" ; unary_op = "-" | "!" | "~" ;
Arithmetic operators
TODO: document this |
Symbol Name Available on + sum integers, floats, complex values, strings - difference integers, floats, complex values * product integers, floats, complex values ** pow integers, floats, complex values / quotient integers, floats, complex values % remainder integers & bitwise AND integers \| bitwise OR integers ^ bitwise XOR integers << left shift integer << integer >= 0 >> right shift integer >> integer >= 0
Integer operators
TODO: document this |
Integer overflow
TODO: document this |
String concatenation
TODO: document this |
Comparison operators
== equal != not equal === type equal !== not type equal < less <= less or equal > greater >= greater or equal
TODO: document this |
Logical operators
TODO: document this |
&& conditional AND p && q is "if p then q else false" || conditional OR p || q is "if p then true else q" ! NOT !p is "not p"
Casting
"Cast" expressions try to change one value from a specific type into another one that is specified. What exactly is being done to convert or "cast" the value to the desired type is based on both the type to be converted, and the type it should be converted into.
CastExpr = "cast" Expression "to" Type ;
The rules for conversion are:
A integer type as input can be cast to any integer type that is bigger in size; they also can always be converted between the signed and unsigned variants as long as both types are the same size, or the target integer type is bigger.
Integer and Character types as input can be cast to each other; i.e. integer to char and char to integer. They only can however be converted as long as the target type has the same byte-size.
Complex types:
A input shape type can:
be casted to a role to create a role instance if the shape source implements the role
TODO: more rules! |
Self expressions
The "self" expression is refering to the current instance when inside a structured types methods or properties.
SelfExpr = "self" ;
Super expressions
The "super" expression is refering to the parents of the current shape types.
If no specification is added, lapyst tries to automatically resolve which parent type is ment. If this fails however, an error is generated.
When on the other hand a specification is added, it can either be:
the name of the parent type. If this is the case, everything is resolved against that particular parent.
a single
*
. This is called a "wildcard spec", and will expand the expression to all parents in the order they’re declared. If one of the expanded expressions is not valid, the affected ones are silently discarded.
SuperExpr = "super" [ "<" ( "*" | QualifiedIdent ) ">" ]
super(); super<A>(); super<*>(); super.i = 0; super<A>.i = 0; super<*>.i = 0; super.method(); super<A>.method(); super<*>.method();
InstanceOf expressions
The "instanceof" expression checks if a expression’s type is a instance of a specifc complex type.
InstanceOfExpr = TertiaryExpr "instanceof" Type ;
When the expression is: - a shape, and the type is * a role, then true is returned when the shape implements the role. * a shape, then true is returned when the expression’s shape is extending the given shape. - a role instance, and the type is * a role, then true is returned when the complex type backing the role instance implements the role. * a shape, then true is returned when the complex type backing the role instance is also a shape and is extending the given shape.
Returns false in any other case.
New expressions
TODO: document this |
NewExpr = "new" QualifiedIdent "(" ArgumentList [ "," ] ")" ;
Drop expressions
TODO: document this |
DropExpr = "drop" identifier [ "," ArgumentList ] ;
Arguments
ArgumentsExpr = "arguments" ;
It’s a special variable-like keyword that behaves like an array of type any
with wich you can get all parameters of the current function via their index, starting at 0.
Ternary expressions
TODO: might get replaced with if -expressions |
TernaryExpr = AssignExpr "?" AssignExpr ":" AssignExpr | AssignExpr ;
Constant expressions
TODO: document this |
Expression precedence
Precedence of operators and expressions is ordered as follows, from strong (top-most) to weak.
Operator / Expression | Associativity |
---|---|
- | |
- | |
- | |
Unary | - |
| left to right |
| left to right |
| left to right |
| left to right |
| left to right |
| left to right |
| left to right |
| left to right |
| left to right |
| right to left |
- |
Binary operators in the same precedence level associate from left to right, i.e. a / b * c is the same as (a / b) * c . |
Order of evaluation
TODO: document this |