Expressions

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 } ;

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:

Slice expressions

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

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.

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:

  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

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

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"

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

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 ) ">" ]

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

NewExpr   = NewExprSm | NewExprEx ;

NewExprSm = ( "new$" | "new@" | "new%" ) QualifiedIdent "(" ArgumentList [ "," ] ")" ;

MemoryLoc = "[" ( "$" | "@" | "%" | QualifiedIdent ) "]"
NewExprEx = "new" MemoryLoc? QualifiedIdent "(" ArgumentList [ "," ] ")" ;

This expression creates a new instance of the type given. How is depending on the type itself. Generally is the result a pointer / reference to the created instance:

NoteTODO: document array, slices, maps, enums

By default, new A() creates a new pointer to A (i.e. A*), by utilising the default allocator currently configured. However, there are ways around that to further specify how memory should be aqquired for the new instance:

  • new$ A() and new [$] A() use the core::alloc::Mallocator allocator, which effectively uses the systems libc allocator implementation via the malloc, realloc and free functions. This requires the resulting binary to be linked against libc.so.

  • new@ A() and new [@] A() use the default GC allocator. (TODO)

  • new% A() and new [%] A() use the core::alloc::Alloca allocator, which allocates the instance on the stack. Note that this means the lifetime of the instance is bound to the function itself. Due to that, the lapyst languages uses escape analysis at compiletime to create error when a stack allocated reference escapes it’s boundaries.

  • new [x] A() can be used with any variable to create a more dynamic way of allocating memory. What exactly happens is based on the type of the variable itself:

    • If x is an instance of core::alloc::Allocator, the allocator is used to retrieve a memory region.

    • If x is an instance of core::alloc::Memory, it is checked if it has enough space to contain the type wanting to use and then uses the contained pointer for the instance.

    • If x is an void*, the memory it points to is used for the instance. Note: this is unsafe as it’s allows to use ANY memory location without checking for size constraints.

Drop expressions

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

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

<< >>

left to right

&

left to right

| ^

left to right

< > <= >=

left to right

== === != !==

left to right

&&

left to right

||

left to right

= += -= /= *= **= %= &&= ||= &= |= <<= >>= ^=

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