Declaration   = ConstDecl | VarDecl ;
TopLevelDecl  = Declaration | Functions | ShapeDecl | RoleDecl | EnumDecl | NamespaceDecl ;

The scope of any declared identifier is the range of sourcecode in which the identifier refers to the declaration of it.

Following scopes exists:

  1. For [predeclared_identifiers] this is every sourcecode there is.

  2. For any declaration inside a Unit this is the unit itself.

  3. For any declaration inside a Namespace this is the namespace.

  4. For any parameter declaration this is the function body (if it exists).

  5. For any type parameter declaration this is the range of the entity the parameter is declared for.

  6. For any declaration inside a shape, enum or role, this is the entire entity declared.

  7. For any constant or variable identifier declared inside a function, it begins at the end of the constant or variable declaration and ends at the end of the innermost block containing said declaration.

Predeclared identifiers

Some identifiers cannot be used, due to them being pre-declared. These are mainly builtin types, constant values and ofcourse all keywords.

    int8 int16 int32 int64
    byte short int long
    uint8 uint16 uint32 uint64
    ubyte ushort ulint ulong
    char8 char16 char32
    char wchar dchar
    void bool
    cstr carray

    true false nil

Uniqueness of identifiers

A identifier is called unique if it is different from every other in a set of identifiers. Two identifiers differ when:

Otherwise, they are the same. Using the same identifier inside two different declarations is considered invalid code and results into a error at compile time.

Constant declarations

A constant declaration binds a constant value to an identifier to reference the same constant value via a more human-readable name. The expression can be any expression which results into a constant value, which means that it cannot change regardless of any state or other value inside the program.

ConstDecl = "const" Type identifier "=" Expression ";" ;

A general rule of thumb is that any literal value and their operations can be used. Any other value, like instances of user-defined types or values that are the result of a function call, are implementation dependent, and are ONLY allowed if the type-construction / functions have no side effects and are pure.

ImportantThe reference compiler lapystc currently does not allow any user-defined types or function calls inside values for constants.

Variable declarations

A variable declaration creates a variable, bounds a identifier to it as it’s name and gives it a type and a initial value.

VarDecl = "var" Type identifier [ "=" Expression ] ";" ;


Functions in lapyst can either be declared or defined.

Functions = FunctionDecl | FunctionDef ;

Function declaration

A function declaration is when we declare the name of a function, as well as it’s result & parameter types. This function contains no code and is only used to tell lapyst that a particular function exists.

FunctionDecl = "dec" Type identifier "(" ParameterList ")" ;
NoteThis feature is often used with lapyst’s feature of integrating other languages.

Function definition

Similar to a function declaration, a function definition also declares the name of a function and result + parameter types, but it also defines the code that the function contains & will be executed on calling said function.

FunctionDef = "def" Type identifier "(" ParameterList ")" Block "end" ;


Function declarations can be overloaded. To do this, simply declare a new function with the same name, but with a different signature.

ImportantFor overloading, only the parameters are relevant in the signature, overloading based on the returntype is not valid.

Parameter declaration

Parameters declare the inputs of a function; they use a identifier to bind a name to a type and a initial value.

ParameterList         = ParameterOrVarargDecl { "," ParameterOrVarargDecl } [ "," ] ;
ParameterOrVarargDecl = ParameterDecl | VarargDecl ;
ParameterDecl         = Type identifier [ "=" Expression ] ;


There are two variants of vararg declarations: a typed vararg and a untyped vararg declaration.

VarargDecl = [ Type ] "..." identifier ;

If a type is added before the …​ token, it is an typed vararg, which makes the parameter a typed iterateable of the specified type. Otherwise it is an iterateable with the type of any.

Theres also a special arguments keyword.

Type parameter declaration

With a type parameter list, type parameters of a generic function or type are declared. Each entry of this list can either be a value type-parameter or a type type-parameter.

TypeParamsDecl       = "[" TypeParamDecl { "," TypeParamDecl } "]" ;
TypeParamDecl        = TypeParamLiteral | TypeParamTypeDecl ;
TypeParamLiteralDecl = ( "const" | Type ) identifier [ "=" Expression ] ;
TypeParamTypeDecl    = identifier [ "=>" identifier { "," identifier } ] [ "=" identifier ] ;

The difference between the two is simple:

  • a value type-parameter is a constant, compile time known value that is used in the generic declaration. Allows to specify a default value to be used; the expression needs to be constant and be calculateable at compile time.

  • a type type-parameter is another type which can be used to substitute types inside the generic declaration. This can constaint any number of constraints after the ⇒, including parent types for inheritance or roles needing to be implemented. Allows to specify a default type to be used if none is supplied.

shape StaticArray of [ T , ulong size ]
    carray[T, size] content;
    def ulong length()
        return size;

Structured member declarations

Field declarations

Declares a field.

FieldDecl = [ "static" ] "var" Type @( Visibility identifier ) [ "=" Expression ] ";" ;

Method declarations

Declares a method.

Method     = MethodDecl | MethodDef ;
MethodDecl = "dec" [ "static" ] Type @( Visibility identifier ) "(" ParameterList ")" ;
MethodDef  = "def" [ "static" ] Type @( Visibility identifier ) "(" ParameterList ")" Block "end" ;

Property declarations

Declares a property.

Property           = [ "static" ] "prop" Type @( Visibility identifier ) [ PropertyGetter ] [ PropertySetter ] "end" ;

PropertyGetter     = PropertyGetterDecl | PropertyGetterDef ;
PropertyGetterDecl = "dec" Type "get" "(" ")" ;
PropertyGetterDef  = "def" Type "get" "(" ")" Block "end" ;

PropertySetter     = PropertySetterDecl | PropertySetterDef ;
PropertySetterDecl = "dec" "set" "(" Type identifier ")" ;
PropertySetterDef  = "def" "set" "(" Type identifier ")" Block "end" ;
NoteIt is implemention defined if the compiler implements getter/setter by closely parsing this as specified or by parsing "normal" functions and then emit errors for non-compliant getter/setters. As an example, lapystc is doing the later.

Constructor declarations

Declares an constructor.

Constructor     = ConstructorDecl | ConstructorDef ;
ConstructorDecl = "dec" @( Visibility "self" [ "::" identifier ] ) "(" ParameterList ")" ;
ConstructorDef  = "def" @( Visibility "self" [ "::" identifier ] ) "(" ParameterList ")" Block "end" ;
shape A
    dec self();
    dec self::other();

Destructor declarations

Declares an destructor.

Destructor     = DestructorDecl | DestructorDef ;
DestructorDecl = "dec" Type @( Visibility "~" "self" ) "(" ParameterList ")" ;
DestructorDef  = "def" Type @( Visibility "~" "self" ) "(" ParameterList ")" Block "end" ;
shape A
    dec void ~self();

Shape declarations

Declares a shape type.

ShapeDecl     = "shape" identifier [ "of" TypeParamsDecl ] [ InheritanceDecl ] { ShapeBodyDecl } "end" ;
ShapeBodyDecl = Constructor | FieldDecl | Method | Property | IncludeStmt ;

InheritanceDecl = "use" "[" Type { "," Type } "]" ;

Visibility = [ "!" | @( "*" { "*" } ) ] ;

Role declarations

Declares a role rype.

RoleType   = "role" identifier { RoleElem [ ";" ] } ;
RoleElem   = FunctionDecl ;

Enum declarations

Enum declarations declare special shapes which only allow a predefined set of instances to exist.

NoteTODO: tinker with these to see how "backed" enums should work…​

Namespace declarations

A namespace declaration is used to group together other declarations. It also adds the name of the namespace before all names of declarations inside them before it in the fully qualified name of these declarations.

NamespaceDecl = "namespace" identifier { TopLevelDecl } "end" ;
namespace MyLib
    # With 'MyLib::myFunc' the rest of the code
    # can now refer to this function.
    def void myFunc()

Module declarations

Declares a reuseable portion of code that even can refer to things that are only present in the place where they’re included. To include a module, see the include statement.

ModuleDecl = "module" identifier Block "end" ;
module A
    def say()
        # ...
back to top