Introduction

Feder is an imperative programming language, which focuses on runtime speed, simple, yet powerful design and more reliable programs. As such, Feder features

  • Garbage collection: Don't care about memory-management
  • No null pointers: Prevent typical errors from languages like C, C++, Java
  • Tuples & tuple-based enumerations: Functions can return several objects, no need for a custom class or state-bound objects
  • Templates
  • Parts from functional programming
  • Parts from object-oriented languages
  • Ensurance (Contracts): Compile-time runtime-exceptions

Target audience

Targeted are those, who seek in-depth knowledge about Feder. This is by no means a guide for beginners.

Example program

A hello world program

import std

func main
	null io.out.println("Hello, World!")
;

which prints

Hello, World!

to standard output.

Lexical elements

Identifiers

Identifiers are character sequences for naming variables, functions, classes, enumerations, traits and namespaces. Identifiers are represented a sequence of the decimal digits, letters and the '_' character.

An identifier mustn't start with a decimal digit. Also the whole sequence must not have only '_' as characters. After leading '_' there mustn't be a digit.

Keywords

Keywords are reserved identifiers for syntax elements which cannot be used for anything else. These are:

func module class enum trait type return use lambda
if else match switch for do continue break safe
True False
_

Constants

A constant is a literal numeric or character values, like 'a' or 5. They all have a data type.

Integers

Integers are digit sequences, which depending on the prefix, allow different characters. The digit sequences must be at least one digit long (prefix excluded).

Decimal integers, which don't have a prefix, but cannot start with 0. They use the decimal digits. Example:

0
100
44
// but not: 0

Hexidecimal integers (base 16) start with the prefix '0x'. They use decimal digits and the letters A - F, both lowercase and uppercase. Example:

0xFF
0x10
0xFA1
// but not: 0x

Octal integers (base 8) start with the prefix '0o'. The use the digits 0 - 7. Example:

0o1
0o71
// but not: 0o

To further denote the data type of the integer, suffixes are used. If no suffix is used the data type is int32.

  • u: Can be front of the suffixes s, S, l and L. Denotes that the data type is unsigned.
  • s: Very short. Data type is i8 or u8 if preceeded by u.
  • S: Short. Data type is i16 or u16 if preceeded by u.
  • l: long. Data type is i32 or u32 if preceeded by u.
  • L: Very long. Data type is i64 or u64 if preceeded by u.

All other letters are invalid, resulting in an error. So

0z
0h
0j

are considered invalid. Also letters after the suffix are invalid. Therefore

0ula
0la
100sD

are all invalid.

Float-point numbers

Floating-number are two decimal digit sequences separated by '.'. Both sequence must have at least one digit. Example:

0.0
0.123
123.5

To further denote the data type of the floating-point number, suffixes are used. If no suffix is used the data type is float64.

  • f: Single precision float. Data type is float32.
  • F: Double precision float. Data type is float64.

All other letters are invalid, resulting in an error. So

0.0a
0.1d

are considered invalid.

Letters right after the suffix are also invalid. So

0.0fx

is invalid.

Characters

A character constant is enclosed within single quotation marks, such as 'a'. Between the quotation marks is usually a single printable character, except character which cannot be represented, like the single quotation mark itself. They are represented with escape sequences, which start with \.

\'
  Single quotation mark

\"
  Double quotation mark

\\
  Backslash

\a
  Bell

\b
  Backspace

\f
  Formfeed page break

\n
  Newline (line feed)

\r
  Carriage return

\t
  Horizontal tabulator

\v
  Vertical tabulator

\xXX
  Byte represented with two XX hexadecimal characters

All other character following \ are invalid.

Strings

A string constant is a character sequence, which is enclosed within double quotation marks. The sequence is zero or more characters long.

The escape sequences of operators can be used within strings, too. Their return-value is NativeString.

Example:

"Hello, World!"
"....\n"
"\"my string\""

Operators

These are tokens representing an operation or function. More about operators in Operator expressions. Operators also act as token separators.

Separators

Characters, which separate tokens are the line-feed and carriage-return character and also:

( ) [ ] { } ;

Later on, the line-feed character and carriage-return character are simply refered to as newline characters.

Also line-comments are treated the same as newline characters. They are started with // and range till the next newline character. Therefore

myFunction()

is the same as

myFunction() // this is a function call

Operators are also separators.

Space

Space characters are the space character, the tab character, the vertical tab character and form-feed character and region-comments. Space characters are ignore, therefore is optional, except when used to seperate tokens.

These means, the following expressions

1 + 2

and

1+2

do the very same at runtime.

Spaces are not ignore in strings or character constants. Therefore

"my      token"

isn't the same as

"my token"

Also newlines following a newline are regarded as space. This means the expressions

trait MyTrait
	Func myfunc ;
;

and

trait MyTrait

	Func myfunc ;
;

represent equal semantics.

Expressions

This chapter specifies the syntax and semantics.

Notation

Non-terminals are written italic and literal terminals are written in bold style. Numbers in terminals are used as indices. The syntax is defined with the BNF with operator-precedence and expressions are defined with the operator :=. Z represents notation for no-characters.

Ignored characters

Between every expression can be space (vertical,horizontal tabulator and the space character). In case space must be used, it the space expression will be used.

Characters between '//' and end-of-line will be ignored, including '//' and end-ofline. Also characters between '/*' and '*/' will be ignored, including '/*' and '*/'. These ignored characters are called comments. Where a comment started with '//' will be interpreted as an end-of-line/newline and a comment started with '/*' as space.

Library name

The optional first expression of an program is progname. This specifies where all top-level public semantics in the file should be added to. if progname is not used, the program's semantics won't be visible to other files.

Syntax specification

The starting symbol is program. The terminals where defined in the chapter Lexical Elements.

int := Integers
float := Floating-point numbers
num := int | float
str := Strings
char := Characters
literal := num | str | char
id := Identifiers
EOF := No characters available
EOL := line-feed carriage-return | carriage-return line-feed
| carriage-return | line-feed
| Comment starting with //
newline := EOL | EOL newline

idcall := id | id . idcall
idcalls := idcall | ( idcall , idcallm )
idcallm := idcall | idcall , idcallm

program := progname newline program-body | progname EOF
| program-body EOF | EOF
progname := use module id

General program:

program-body := def newline | def EOF | def newline program-body
| use newline | use EOF | use newline program-body
def := func | trait | class | traitimpl | enum | module | vardef | capsdef exprdef := func | trait | class | traitimpl | enum | capsdef

Use:

use := use idcall | use idcall . *

Templates:

template := { templdecls }
templdecls := templdeclslimit , templlasttype | templatedeclslimit | templlasttype
templdeclslimit := templdecl | templdecl , templdeclslimit
templdecl := id | id : templidcall | id0 : templidcall* := templidcall
templlasttype := id | id : templidcall ... | id0 : templidcall ... := templidcall
templidcalls := templidcall | templidcall , templidcalls | templidcall , newline templidcalls
templidcall := idcall | idcall template

Functions:

func := func funcdecl newline ; | func funcdecl = expr newline
| func funcdecl newline funcbody ;
| func funcdecl : returntype newline retfuncbody ;
| func funcdecl : newline retfuncbody ;
| funcnb
funcnb := func funcdecl ; | func funcdecl : returntype ;
funcdecl := template funcdeclx | space funcdeclx
funcdeclx := id | id ( funcvars )
funcvars := funcvar | funcvar , funcvars
funcvar := vardecl | vardecl guard
guard := | expr0 | | expr0 = expr1
returntype := templidcall | vardecl
returntypetuple := ( returntuplex )
returntuplex := returntupleunit | returntupleunit , returntuplex
returntupleunit := templidcall | id : templidcall | func | functype
funcbody := stmt | stmt newline funcbody
retfuncbody := funcbody return expr newline
functype := func type | func type : return-type | func type ( funcvars ) | func type ( funcvars ) : return-type

clfunc := func clfuncdecl newline ;
| func clfuncdecl : returntype newline retfuncbody ;
| func clfuncdecl : newline retfuncbody ; | func clfuncdecl = newline expr ;
| func clfuncdecl : returntype = newline expr ;
| clfuncnb
clfuncnb := func clfuncdecl ; | func clfuncdecl ; returntype ;
clfuncdecl := template clfuncdeclx | space clfuncdeclx
clfuncdeclx := id ( funcvars )

Lambdas:

lambda := lambda ( vardecl ) newline retfuncbody ;
| lamda ( vardelcs ) newline funcbody ;
| lambda newline retfuncbody ;
| lamda newline funcbody ;
| lambda = expr | lambda ( vardecl ) = expr

Traits:

trait := trait traitdecl newline traitbody ; | trait traitdecl newline ;
traitdecl := space id | template id | space id : templidcalls | template id : templateidcalls
traitbody := clfuncnb | clfuncnb newline traitbody

Classes:

class := class classdecl newline classbody ;
| class classdecl classcons newline classbody ;
classdecl := space id | template id
| space id : templidcalls | template id : templidcalls
classcons := ( vardecl )
classbody := classunit | classunit newline classbody
classunit := vardecl | caps vardecl | clfunc | caps clfunc

traitimpl := class space trait traitimpldecl newline traitimplbody ;
traitimpldecl := space idcall : templidcall | template idcall : templidcall
traitimplbody := clfuncnb | clfuncnb newline clfuncnb

Enumerations:

enum := enum enumdecl newline enumbody ;
enumdecl := id
enumbody := enumunit | enumunit newline enumbody
enumunit := id | id ( templidcalls )

Modules:

module := module moduledecl newline program-body
moduledecl := id

General Expression:

expr := exprdef | ( expr ) | biopexpr | unopexpr | fctl | lambda | expr ( expr ) | expr [ expr ] | array | safe | id | literal
stmt := expr | vardecl | vardef | stmt ; stmt

Variable declaration:

vardecl := vardeclunit | vardeclunits
vardeclunits := vardeclunit | vardeclunit , vardeclunits
vardeclunit := id : templidcall | & id : templidcall
| id : functype | & id : functype varids := varid | varid , varids
varid := id

Variable definition:

vardef := vardecl = expr | vardefid := expr | ( vardefids ) := expr
vardefids := vardefid | vardefid , vardefids
vardefid := & id | id

Arrays:

array := arraylit | arraycpy | arrayfor
arraylit := [ expr0 , expr1 ]
arraycpy := [ expr0 ; expr1 ]
arrayempty := [ expr ]
arrayfor := [ for id : expr0 newline retfuncbody ]

Capabilities:

caps := # capid newline | # capid newline caps
| #! capsensure newline | #! capsensure newline caps
capid := Unused | Inline | Constant | Internal
capsensure := requires space ensurecond | ensures space ensurecond | ensures space ensurecond0 => ensurecond1
capsdef := caps func | caps class | caps enum | caps vardef

Control-flow expressions:

fctl := cond | loop
cond := if | match | ensure
loop := for | do

If expressions:

if := ifbase else ; | ifbase ;
ifbase := if space expr newline funcbody newline
| if space expr newline funcbody newline loopctl newline
| if space expr newline retfuncbody newline
else := elsebase | else elsebase
elsebase := else space expr newline funcbody newline
| else space expr newline retfuncbody newline

Ensure expression:

ensure := ensureunit ; | ensureunit else ensure | ensure else newline funcbody ;
ensureunit := ensure ensurecond newline funcbody
ensurecond := idcall ensurecondop expr
ensurecondop := == | != | ~= | > | >= | < | <= | ::

Match expression:

match := matchbase newline matchbody newline ;
matchbase := match expr
matchbody := matchcase | matchcase matchbody
matchcase := matchcasecond => funcbody ; | matchcasesecond => retfuncbody ;
matchcasecond := matchcasecons | matchcasecons expr
matchcasecons := matchcon | matchcon ( matchids )
matchcon := id | num | char | True | False matchids := matchid | matchid , matchids
matchid := _ | id

Loop expressions:

loopctl := break | continue | break int | continue int
for := for newline funcbody ;
| for expr newline funcbody ;| | for expr ; expr newline funcbody ;
| for expr ; expr ; expr newline funcbody ;
do newline funcbody ; for expr
| do expr newline funcbody ; for expr
| do expr ; expr newline funcbody ; for expr

Safe expression:

safe := safe templidcall ( expr )
| safe arraylit

Operators:

biopexpr := expr0 biop expr1 | expr0 expr1
unopexpr := lunop expr
biop := + | - | * | / | ** | %
| & | | | ^ | << | >>
| == | != | ~= | < | > | <= | >=
| && | || += | -= | *= | /= | *= | %=
| &= | |= | ^= | <<= | >>=
| <>
| = | . | ->\ | , lunop := * | ! | null | + | - | ++ | --

Environment

An environment is assembly of semantics1 which can be referenced by an identifier2. There can be semantics with the same identifier, when this is the case, a reference will point to the newest semantic. The environment has either no parent (if it is the environemnt of the whole document) or one parent.

1

All function bodies, program-bodies, modules and classes(only functions) have an own environment.

2

This is also funcvars or template not just the id.

Shadowing

As mentioned above, semantics with the same identifier can be added to a single environment. This is only possible, if:

  • The previous semantic in the environemnt is a variable
  • The semantic is an template specialization of of the previous semantic (where all semantics which are already specializations are ignored).

Visibility

Semantics starting with _ are only visible to children environments. All other semantics are can always be referenced if not shadowed.

Operators

The less precedence an operator has, the less it binds.

Operator associativity:

  • l: Operator is left associative
  • r: Operator is right associative
OperatorPrecedenceAssociativityDescription
,1lComma, Tuple/argument separator
:=2rDeclaring assignment
&= ^=3rOn-object operation
|=3rOn-object operation
<<=3rOn-object operation
>>=3rOn-object operation
%=3rOn-object operation
/= *=3rOn-object operation
+= -=3rOn-object operation
=3rVariable assignment
null a4rMake empty, returns ()
||5lLazy-logical or
&&6lLazy-logical and
<>7lArgument binding
expr expr7lFunction call
|8lBitwise or
^9lBitwise xor
&10lBitwise and
== !=11lDirect value equaltity
~=11lCompare (used by Equals)
< <=12lLess, Minimum
> >=12lGreater, Maximum
>> <<13lBitwise right/left shift
a+b a-b14lAddition, substraction
a%b14lRemainder
a*b a/b15lMultiplication, Division
:16rVariable declaration
++a --a17rIncrement, Decrement
+a -a17rPositive,Negative
! ~17rLogical not, Bitwise not
&a17rMutable
*a17r"Dereference"
a()18lFunction call
a{}18lTemplate call
a[]18lIndex call
.18lMember access
->18l"Dereference" member access

Anonymous semantics

Every def (except vardef) not used in a top-level expression is an anonymous semantic, its reference will not be added to the current environment.

Modules

Modules separate code by functionality. A module has an own environment and is added as id from moduledecl to the parent environment. A module's id must start with an lowercase, alphabetic character.

Libraries are also just modules.

Example:

module hello
	func world
		null io.println("Hello, World!")
	;
;

func main
	hello.world() // Prints hello world to standard output
;

Variables

Variables in modules can be either global, if the module has only modules and library as parents otherwise the variable is local.

Non-primitive, global variables are initialized when first called (lazy-initialization).

Examples:

module mymod
	class Person (name : String,
		          age : u8)
		func getName(this : This) =
			name;
		func getAge(this : This) =
			age;
	;

	myglobvalue := 100          // primitive global variable
	me := Person("Someone", 16) // me is not initialized !!!
;

func main
	io.println(mymod.myglobvalue)
	io.println(mymod.me.getName()) // Initialized mymod.me
;

Use

The first id in idcall is either a module or library (libraries are declared with progname). If use ends with .*, then all public semantics of idcall are accessible in the current environment and all children environemts otherwise only the semantic referenced by idcall can be accessed.

Example:

hello.feder:

use mod helloprinter
use std.io

func printHello
    io.out.println("Hello, World!")
;

program.feder:

use helloprinter

func main
    io.out.println("Hello, World!")
;

Functions

Normal functions can't use non-global variables from parent environments.

A function beginning with a lowercase character with possible leading '_', no function with the same parameters and function name must exist in the current environment. Otherwise, if the function starts with a uppercase character with possible leading '_', no other semantic with the same name must exist in the current environment.

If the idcall before the function's parameters consist of multiple ids the function called like the most right id is added to the environment referenced to be the ids in fron of the last one. The environment has to be a class, module or primitive datatype.

A function with no retfuncbody or return-type returns the empty tuple (). A function with a return type (a function with : after the parameters or function name) must not have the return type () (empty type). Every element of the return-type must be pointer immutable.

Only functions which are declared with the Unique capabilitiy can be used as an object, all others must only be used for complete function calls.

Example (functions without return type):

// print hello world
func printHello0(out: OutputStream)
  null out.println("hello, world!")
;

// multiple instructions
func printHello1(out: OutputStream, User: String)
  null out.print("hello, ")
  null out.print(user)
  null out.print("!")
;

Example (functions with return type):

/*
 * How to write a function which returns the sum of the first and second
 * argument in Feder
 */

// sum function with **return** keyword and explicit return-type
func add0(x: int32, y: int32): int32
  return x + y
;

// sum function with **return** keyword and implicit return-type
func add1(x: int32, y: int32):
  return x + y
;

// sum function without **return** keyword but explicit return-type
func add2(x: int32, y: int32): int32 =
  x + y
;

// sum function with **return** keyword and implicit return-type
func add3(x: int32, y: int32) =
  x + y
;

Guards

The expr0 of the guard has to evaluate to an object with the type bool.

The guard is an ensurance, if => is not used. The given argument must be ensured to be compatible. The => evaluates and returns expr1 if expr0 returns False.

Example:

func div(x: int32, y: int32 | y != 0): int32 =
  x / y
;

// Using the

Argument binding

The operator <> is used to bind a function (LHS), which is also a variable/anonymous function, to an object (RHS) which replaces the last parameter. The function must have at least one parameter. The returned function is is missing the last parameter. Values are bound by using call-by-value.

// normal integer addition
func add(x: int32, y: int32): int32 =
  x + y
;

// sum number one and argument and return result
add1 := add <> 1

func main
  null io.out.println(add1(2)) // prints 3 to standard output
;

Function body

Every expr (but not expr in expr) is a statement. These statements must return () as type or an L-value. Otherwise the expr is invalid. This can be fixed with the null operator.

Function call

A function is called with expr ( x ) where x is either nothing, a value or tuple of values. The expr value count must be the same as the parameter count of the function declaration. Values (and variables) are passed by using call-by-value.

Example:

func add(x: int32, y: int32) =
  x + y
;

func main
  null add(2, 3)
;

Invalid add function calls:

add(2) // invalid argument count, no add(x: int32) function declaration
add(2.0f, 3) // invalid argument 2.0f (not type int32),
             // no add(x: float32, y: int32) function declaration

Traits

They are added to the current environment using the id in traitdecl. Traits have an own environment which can't be referenced, which contains the template variables.

The environment of an object which has the trait as type, contains all functions declared in traitbody. When referencing a function, it is returned without the this or This parameter.

The name of the trait (first expression id in traitdecl) must start with an uppercase character with optional leading '_'.

Trait inheritance

Traits can inherited function declarations from other traits with templidcalls, where every templidcall must be a trait. Traits can't be inherited if a the traits have functions with the same name and parameters.

Templates

Every behaviour of an object, which have the template type as type, must be provided by the template variable. This is realized with traits, which declare the behaviour.

Semantics which declare templates, must have defined behaviour. They cannot have declared behaviour.

It is possible to use the template type in its own template definition. Template types cannot be access by previous template types.

If id1 is used, then id0 can be omitted, when resolving the template. id1 must be a valid template type used in previously in the same template declaration or a class or trait.

If a template type is followed by ..., the template accepts after the initial template types an abitrary number of types (at least 0). Must be last argument in template declaration.

class{T: std.StdField} MyVectorClass0
  x: T
  y: T

  func MyVectorClass0(this, x: T, y: T)
	this.x = x
	this.y = y
  ;
;

class{T, D=T} MyTemplateClass0
;

func main
  var0 := MyTemplateClass0{std.u8}();
  var1 := MyTemplateClass0{std.u8, std.u16}();
  var2 := MyTemplate
;

Example with ...:

class{T...} Tuple
  tplvalue: T
;

Variables

Private and mutability:

  • Variables starting with '_' are private variable and can only be reached by the current environment and current environment's children

  • Variables starting with an uppercase character (optionally with leading '_') are called value immutable. Their environment has only immutable functions (functions starting where the first parameter is value immutable) and immutable variables, where mutable variables in the original environment will also be immutable.

  • Variables which have the type &expr are pointer mutable. They can be reassigned after initialization. Variables with other types cannot be reassigned. Conversion between pointer (im-)mutable types is done implicitly.

Example:

&x : i32 := 100
&x := 100
x = 101 // works
y := 100
y = 101 // doesn't compile

Representing an object

A variable returns () if it isn't initialized. A variable can be initialized using = or := to declare and define the variable at the same time.

Assignments

Assigning a variable with = (re-)initializes the variable. Reinitialization 1 is only possible if variable is pointer mutable. The assigned object (RHS) is returned.

Assigning a variable with := declares the variable and the initializes the variable. Returns ().

1

The variable is already initialized.

Local and global

Local variables are dropped after a funcbody scope ends. Global variables are dropped when the program terminates.

Shadowing

With shadowing identifiers used by previous variable declarations can be used in another variable declaration. Afterwardss, as long the new variable isn't dropped, this variable will be referenced when mentioning the identifier instead of the old one. A variable can only be shadowed if it was declared in a parent scope.

Initialization

The value of a funcbody variable can be used, if the variable was definitly initialized, otherwise only initialization is allowed. The initization of variables in funcbody can only be done in the scope the variable was declared.

Classes

Classes have attributes, constructors and functions:

  • Attributes: Every vardecl in classcons and classunit which is a vardecl. Added to the environment of the an object which has the type of the class.
  • Constructors: classcons is a constructor which has vardecls as parameters, where every attribute declared in classcons is assigned with the corresponding argument (the exact same). All other constructors are the class's functions which have the exact same name as the class. Every constructor with the additional _init function must initialize all attributes declared in the class. The function called _init will always be called before the constructor function. Constructor functions mustn't have a return-type. Immutable variables can be assigned once in the constructor. Contructors are added to the parent environment of the class.
  • Functions: Every classunit which is a func, which isn't a constructor.

The name of the class (second expression id in classdecl) must start with an uppercase character with optional leading '_'.

Examples:

class Person
	&_Name : String
	&_age : u8

	func Person(This, Name : String, age : u8)
		._Name = Name
		._age = age
	;

	func getName(This) =
		._name;
	
	func getAge(This) =
		._age;
;
// A bit less code ...
class Person(&_name : String, &_age : u8)
	func getName(This) =
		._name;

	func getAge(This) =
		._age;
;

Implementing traits

Functions declared can be implemented by classes to support polymorphism. traitimpl must contain all functions declared by the mentioned trait after : in traitimplbody. A trait already implemenated in the functions cannot be reimplemented.

Creating an object

An object is created by making function call where the called function is a reference to the class.

Class environment

Accessing the environment of the class:

  • Every function has every direct attribute and function of the class in the parent environment

  • To access the class environment directly the left-unrary operator . must be used

This

This is the keyword which represents the closest parent class environment as type (same as writing the class name).

Functions

The first parameter of every class function must have the same type as the class. This can be achieved with This. If the first parameter doesn't have a variable name (just the type), then the variable this with current class as type is added to the environment of the function body.

Functions are added to the environment of the class semantic and are added to class' instances without the first parameter.

Enumerations

Enumerations have consist of several constructors. Each constructor can be accompanied by a tuple with types. The tuple can be extracted with the match expression. All constructors are added to the parent environment. An enumeration has an own environment which is not added to the parent environment which stores template variables. A reference to the enum type will be added to the parent of environment. The reference is called like id in enumdecl.

Arrays

Arrays can store multiple elements. The elements are accessed with an index (an uintptr value) starting from 0. arraylit returns Array{type}, where type is the type of the value expr0. Every value in the comma separated expr1 must have the exactly same type as expr0. The length of the array is 1 + count of commas in expr1. arraycpy returns Array{type}, where type is type of the object expr0 and every element of the array has the value of expr0 and the array's length is determined by expr1. expr1 must return the type uintptr. arrayempty returns an array with length 0 with the type Array{type}, where type is an the type referenced by expr. Here, expr mustn't contain a comma (would collide with arraylit. arrayfor's id is an index variable which will have all values between including 0 till excluding value of expr0. Every idth element will be assigned to value returned by retfuncbody.

Example:

Array{int32} myarray = [1,1,2,3]
null std.io.println(myarray[0]) // print 1
null std.io.println(myarray.length()) // print 4

null std.io.println([i32].length()) // prints 0

// for-arrays
squared := [for i : 20
    return i * i
]

null std.io.println(squared[10]) // prints 100

Static-length arrays

Array-types can be set to a definite length. This is done with expr : integer.

Lamda functions

In Feder a lambda is a function which can use variables declared in previous funcbody scopes. Lambdas are treated as functions with bound arguments, where the bound arguments are variables, used in the lambda funcbody, from previous funcbody scopes. These bound variables must be initialized and will be passed by using call-by-reference (can also be achieved with Box{type}). Variables in vardecls are passed by using call-by-value (like normal function parameters).

Example:

status = "0"
printStatus := lambda 
		null io.out.println("Status: " + status)
	;
printStatus() // prints "Status: 0"
status = "1"
printStatus() // prints "Status: 1"

Ensure

The ensure expression can tell the compiler that the variable idcall has specific properties. This can be further used to call functions with guards. If the ensure expression has an else-clause it is possible for the expression to return a value.

Example: Safe division

std.io.out.print("Input LHS value: ")
lhs := std.io.in.readf64()
std.io.out.print("Input LHS value: ")
rhs := std.io.in.readf64()

ensure rhs != 0.0
    result := lhs / rhs
    std.io.out << "Result: " << result << std.io.endl
;
// rhs / lhs fails to compile

Capabilities

Capabilities can modify how a function is called, implemented or changes compile time informations about an object.

Capabilities:

  • Unused: Still implement semantic, even if unused

  • Inline: Function name is removed its own environment (prevents direct recursion). Tells compiler to eventually directly implement function instead of calling it. Classes are used as tuples, which cannot be casted to traits.

  • Constant: Function can be computed at compile time. Only primitive datatypes can be used.

  • Internal: Semantic is handled by the compiler

Requires:

  • Ensure that certain properties are valid. id in ensurecond can also be functions in the same class which only have the class as parameter, next to the attributes of the class.

  • Function cannot be called if one ensurecond is invalid

Ensures:

Ensurance is a way to express the state of an object during compile-time. This allows the programmer to detect invalid state at compile-time.

  • Ensures that after calling the function, certain properties are valid. The id result can be accessed to ensure that the result behaves in a certain way. This also can be conditional on the result, so if the user ensures the ensurecond0, ensurecond1 would automatically be valid.

  • Deductions are made based on the implication ensurecond0 => ensurecond1. If ensurecond0 is valid ensurecond1 is also valid. Then ensurecond1 is checked if it is ensurecond1 in an implication.

  • The idcall in ensurecond or ensurecond1 will be reset before ensurance is made. Meaning if connected == False was ensured and #!ensure connected == True is called, connected == False will no longer be valid.

  • When the condition is a binary expression, the left side is the a compile-time object and the right side it's state. The compile-time object is bound to the runtime-object (object of class, trait, type). If it is unary, the expression will be the compile-time object.

  • Deductions:

    • x == True => x != False
    • x == False => x != True
    • x => x == True
    • !x => x == False
    • x < y => x != y
    • x > y => x != y
    • x < y, y < z => x < z (This makes x > 0 valid for x > 512)

Example:

class Client(_addr : Address)
    _nativeConn : &Option{NativeClientConnection}

    #!requires connected == False
    func _init
        _nativeConn = Option{NativeClientConnection}.None()
    ;

    #!ensures result == False => _nativeConn == Option.None
    #!ensures result == True => _nativeConn == Option.Some
    func connected(This) : bool
        match _nativeConn
            Some(_) => return True ;
            _ => return False ;
        ;
    ;

    #!requires connected == False
    #!ensures result == True => connected == True
    #!ensures result == False => connected == False
    func connect(This) : bool
        _nativeConn = createNativeClientConnection(_addr)
        match _nativeConn
            Some(_) => return True ;
            _ => return False ;
        ;
    ;

    #!requires connected == True
    #!ensures connected == False
    func disconnect
        conn := _nativeConn :: Option.Some
        conn.disconnect()
    ;

    #!requires connected == True
    func send(This, msg : Array{u8})
        conn = _nativeConn :: Option.Some
        conn.send(msg)
    ;

    #!require connected == True
    func receive(This, msg : Array{u8})
        conn = _nativeConn :: Option.Some
        conn.send(msg)
    ;
;

func main
    client := Client(Address("127.0.0.1", 22))
    ensure client.connect()
        std.io.out << "Connected to server" << std.io.endl
        client.send("help the world")
        client.disconnect()
        // client.send, receive no longer callable
    else
        // client.send, receive cannot be called here
        // as ensure connected == True is invalid
        std.io.out << "Connection failure" << std.io.endl
    ;
;

Tuples

A tuple is a primary expression ( expr ), where at least expr is a binary operator , comma expression.

A tuple with & id or id as elements must be the the left hand side of the := assignment. Tuples with vardecl as elements must be the left hand side of the = assignment. Tuples with just types must be a function's result-type. Tuple with just values as elements must be a the function's return-value, but can also be used in normal code. In the last case the are expanded, as if you would have written the contents with surrounding brackets. Thus

myfunc(hello, (1, 2, 3), 4)

is transformed to

myfunc(hello, 1, 2, 3, 4)

The rules are:

  • First every tuple that is in a tuple is expanded, afterwards itself will be expanded
  • As the comma operator is left associative, the most left element will be the first on to be removed from the tuple and added to the left side. While the operator on the left is automatically removed.

Kernel

The Kernel provides basic functionality, like integers or the Option trait.

Data types

Basic data types provided by the kernel:

bool

Bool(ean) data-type. Values are either the tokens True or False.

Operators defined on booleans:

  • bool0 && bool1: Evaluate bool0. If evaluated bool0 is false, return false, otherwise evaluate bool1 and return evaluated bool1.

  • bool0 || bool1: Evaluate bool. If evaluated bool0 is true, return true, otherwise evaluate bool1 and return evaluated bool1.

  • ! bool0: Evaluate bool0. IF evaluated bool0 is true, return False, otherwise return True.

  • expr0 == expr1: Return true, if expr0 and expr1 are True. Also return True, if expr0 and expr1 are False. In all othercases False is returned.

  • expr0 != expr1: Returns the same as
    ! ( expr0 == expr1 )

Integers

  • i8: 8-bit integer (signed octet)
  • u8: 8-bit unsigned integer (unsigned octet)
  • i16: 16-bit integer (signed short)
  • u16: 16-bit unsigned integer (unsigned short)
  • i32: 32-bit integer (unsigned int)
  • u32: 32-bit unsigned integer (signed int)
  • i64: 64-bit integer (signed quad)
  • u64: 64-bit unsigned integer (unsigned quad)
  • iptr: Architecture dependent size (signed)
  • uptr: Architecture dependent size (unsigned). Used for pointers and size of arrays.

More about integers at Wikipedia. Every integer-type starting with u are unsigned, all others are signed.

Unary operators defined on integers:

  • - expr0: Return expr0 multiplied by -1. Return-type is the one of expr0.
  • ~ expr0: Bitwise inverse of expr0. Return-type is the one of expr0.

Binary operators defined on integers:

  • expr0 + expr1: Return sum of expr0 and expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0.

  • expr0 - expr1: Returns sum of expr0 and - expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0.

  • expr0 ** * ** expr1: Returns expr0 multiplied by expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0.

  • expr0 / expr1: Returns expr0 divided by expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0. expr1 must be ensured not to be equal to 0.

  • expr0 & expr1: Return bitwise conjunction of expr0 and expr1. expr0 and expr1 must have the same type. Return-type is the on of expr0.

  • expr0 | expr1: Return bitwise inclusive conjunction of expr0 and expr1. expr0 and expr1 must have the same type. Return-type is the on of expr0.

  • expr0 ^ expr1: Return bitwise exclusive conjunction of expr0 and expr1. expr0 and expr1 must have the same type. Return-type is the on of expr0.

  • expr0 << expr1. Return number expr0 shifted expr1 to the left. expr1 must have the type uint16. Return type is the one of expr0.

  • expr0 >> expr1. Return number expr0 shifted expr1 to the right. If expr0 is a signed-type, the new bits are either 0 (if positive) or 1 (if negative). expr1 must have the type uint16. Return type is the one of expr0.

  • expr0 == expr1: Returns True if numbers expr0 and expr1 are equal (expr0 - expr1 is equal to 0), otherwise False. expr0 and expr1 must have the same type. Return-type is bool.

  • expr0 != expr1: Returns False if numbers expr0 and expr1 are equal (expr0 - expr1 is not equal to 0), otherwise True. expr0 and expr1 must have the same type. Return-type is bool.

  • expr0 ++: Post-increment expr0. Returns expr0 and then executes expr0 += 1.

  • expr0 --: Post-decrement expr0. Returns expr0 and then executes expr0 -= 1.

  • ++ expr0: Pre-increment expr0. Executes expr0 += 1 and then returns expr0.

  • -- expr0: Pre-decrement expr0. Executes expr0 -= 1 and then returns expr0.

Floating-point numbers

  • f32: IEC-559/IEEE-754 32-bit float-point number (float, in IEEE-754: binary32)
  • f64: IEC-559/IEEE-754 64-bit float-point number (double, in IEEE-754: binary64)

More about floats at Wikipedia.

Unary operators defined on integers:

  • - expr0: Return expr0 multiplied by -1. Return-type is the one of expr0.

Binary operators defined on floats:

  • expr0 + expr1: Return sum of expr0 and expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0.

  • expr0 - expr1: Returns sum of expr0 and - expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0.

  • expr0 ** * ** expr1: Returns expr0 multiplied by expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0.

  • expr0 / expr1: Returns expr0 divided by expr1. expr0 and expr1 must have the same type. Return-type is the one of expr0. The value expr1 must be ensured to be not 0.0.

  • expr0 == expr1: Returns True if numbers expr0 and expr1 are equal (expr0 - expr1 is equal to 0), otherwise False. expr0 and expr1 must have the same type. Return-type is bool.

  • expr0 != expr1: Returns False if numbers expr0 and expr1 are equal (expr0 - expr1 is not equal to 0), otherwise True. expr0 and expr1 must have the same type. Return-type is bool.

Enum Option

use module std

enum{T} Option
  None
  Some(T)
;

Functions

Floating-point

use module std

/**@return Returns true, if float is a special IEEE-754 value.
 */
func@Safe isSpecialFloat(float32) : bool;

/**@return Returns true, if float is a special IEEE-754 value.
 */
func@Safe isSpecialFloat(float64) : bool;

/**@return Returns true, if float is a special IEEE-754 value.
 */
func@Safe,Unique isSpecialFloat32(float32) : bool;

/**@return Returns true, if float is a special IEEE-754 value.
 */
func@Safe,Unique isSpecialFloat64(float64) : bool;

/** Terminates program.
 */
func@Safe panic ;

Tuples

Every tuple supports two operations (expr0 and expr1 are tuples filled with defined variables):

  • expr0 == expr1: Only possible if expr0 has exactly the same amount of types as expr1. Also every ith-type in expr0 must be exactly the same as the ith-type in expr1. Returns True, if every direct ith-value in expr0 is the same as the direct ith-value in expr1 (checked with ==), otherwise False.

  • expr0 != expr1: Only possible if expr0 has exactly the same amount of types as expr1. Also every ith-type in expr0 must be exactly the same as the ith-type in expr1. Returns False, if every direct ith-value in expr0 is the same as the direct ith-value in expr1 (checked with ==), otherwise True.

Traits

Build-in traits:

use module std
// the following traits allow a program to use operators with user
// defined types

// Trait, that every class implements
trait Object
;

// binary operator '+'
trait{T,R} Addition
  func Add(rhs: T): R;
;
// binary operator '-'
trait{T,R} Subtraction
  func Sub(rhs: T): R;
;
// binary operator '*'
trait{T,R} Multiplication
  func Mul(rhs: T): R;
;
// binary operator '/'
trait{T,R} Division
  func Div(rhs: T): R;
;
// sum '+', '-', '*' and '/'
// compiler assumes the following is correct:
//   - a + b = b + a
//   - a * b = b * a
//   - a * (b+c) = (a*b) + (a*c)
//   - a * (b-c) = (a*b) - (a*c)
trait{T,R} StdField : Addition{T,R}, Subtraction{T,R},
    Multiplication{T,R}, Division{T,R}
;

// binary operator '~='
trait{R, T} ComparisonEquals
  func Equals(T): R;
;

// binary operator <, >
// operator a > b is replaced by b < a
trait{R, T} Comparison : ComparisonEquals{R, T}
  func LesserThan(T): R;
;

// operator [x]
trait{T, R} Map
  // Expression: *expr0* **[** *expr1* **]**
  // *expr0* is this
  // *expr1* is index
  func at(index: T): R;
;

trait{T, R} MutMap : Map{T, R}
  // Expression: *expr0* **[** *expr1* **]** = *expr2*
  // *expr0* is this
  // *expr1* is index
  // *expr2* is val
  // Returns val 
  func set(index: T, val: R): R;
;

trait{T} Array : Map{std.uptr, T}
  // Access element index (start couting from 0)
  #!requires index < Length()
  func at(This, index: uptr): T;

  // How many elements can be accessed with func []
  func length: uptr ;
;

trait{T} MutArray : MutMap{std.uptr, T}
  // Access element index (start couting from 0)
  #!requires index < Length()
  func set(this, index: uptr): T;
;

trait{T} Assignment
	// Expression: **\*** *expr0* = *expr1*
	// Returns val.
	func set(val: T): T;
;

trait{T: ComparisonEquals{bool, T}} Hash
  func Hash: T
;

trait ScopeDestructor
	// Is called one variable, if it's end of scope is reached
	func leaveScope;
;

Classes

Classes implemented into the kernel:

use module std

class{Type} Box(Var: Type)
    func * =
        Var
    ;
;

class{Type} MutBox(var: Type)
    func * =
        var
    ;
;

class NativeString(_chars: Array{char}, _hash,: u8)
;

class trait NativeString : Hash{u8}
    func hash(This) =
        _hash
    ;
;

class trait NativeString : Array{char}
    func at(This, index: uptr): T =
        _chars[index]
    ;

    func length(This) =
        _chars.length()
    ;
;

MIT License

Copyright 2019-2020 Fionn Langhans

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.