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;

# This is a qualified identifier for
# the 'abc' identifier in
# the 'Test::Inner' namespace

Function literals

NoteTODO: 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 ;
ImportantThe 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'

Slice expressions

NoteTODO: tinker with this first


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 )

myFunc(1, 2, 3, 4)

def void otherFunc( int... numbers, int i )

otherFunc(1, 2, 3, 4)
1This is called with 1, 2, 3, 4 being put into numbers, while nothing is given to other_nums.
2Here 1, 2, 3, 4 is given to numbers while 4 is assigned to i.


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:

  1. For every type parameter, it’s type argument is used as a substitution in the generic declaration; this includes the type parameter list itself.

  2. 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.

NoteTODO: elaborate more + explain type inference


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

NoteTODO: 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

NoteTODO: document this

Integer overflow

NoteTODO: document this

String concatenation

NoteTODO: document this

Comparison operators

==  equal
!=  not equal

=== type equal
!== not type equal

<   less
<=  less or equal
>   greater
>=  greater or equal
NoteTODO: document this

Logical operators

NoteTODO: 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"


"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

NoteTODO: 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.i = 0;
super<A>.i = 0;
super<*>.i = 0;


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

NoteTODO: document this
NewExpr = "new" QualifiedIdent "(" ArgumentList [ "," ] ")" ;

Drop expressions

NoteTODO: document this
DropExpr = "drop" identifier [ "," ArgumentList ] ;


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

NoteTODO: might get replaced with if-expressions
TernaryExpr = AssignExpr "?" AssignExpr ":" AssignExpr | AssignExpr ;

Constant expressions

NoteTODO: document this

Expression precedence

Precedence of operators and expressions is ordered as follows, from strong (top-most) to weak.

Operator / ExpressionAssociativity

Primary expressions


Function calls, array indexing / Secondary expressions


instanceof / Ternary expressions


Unary - ! ~


* ** / %

left to right

+ -

left to right

&lt;&lt; &gt;&gt;

left to right


left to right

| ^

left to right

&lt; &gt; &lt;= &gt;=

left to right

== === != !==

left to right


left to right


left to right

= += -= /= *= **= %= &amp;&amp;= ||= &amp;= |= &lt;&lt;= &gt;&gt;= ^=

right to left

Ternary expressions


NoteBinary 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

NoteTODO: document this
back to top