The EeZee Programming Language

The EeZee programming language is a toy language with just enough features to allow experimenting with various compiler techniques.

The base language is intentionally very small. Eventually there will be extended versions that allow functional and object oriented paradigms.

Language features

  • User defined functions

  • Integer type

  • User defined struct types

  • One dimensional arrays

  • Basic control flow such as if and while statements

Keywords

Following are keywords in the language:

func var int struct if else while break continue return null

Source Unit

The EeZee language does not have the concept of modules or imports. Each source file must be self contained.

There is no predefined main function in a source unit. The runtime should allow any defined function to be invoked by supplying appropriate arguments.

Types

The only primitive type in the language is the integer type Int. The size of this type is unspecified, the default implementation is 64-bit integers.

There is not a distinct boolean type, non-zero integer values evaluate as true, and zero evaluates as false.

Users can define one-dimensional arrays and structs.

Arrays and structs are implicitly reference types, i.e. instances of these types are allocated on the heap.

The language does not specify whether the heap is garbage collected or manually managed, it is up to the implementation.

A struct type is a named aggregate with one or more fields. Fields may of be of any supported type. Struct types are nominal, i.e. each struct type is identified uniquely by its name. Multiple definitions of a struct type are not allowed.

An array type is declared by enclosing the element type in brackets, i.e. [ and ].

There is a Null type, with a predefined literal named null of this type.

When declaring fields or variables of reference types, user may suffix the type name with ? to indicate a Nullable type. A Null is an implicit subtype of all Nullable types.

Examples:

struct Tree {
    var left: Tree?
    var right: Tree?
}
struct Test {
    var intArray: [Int]
}
struct TreeArray {
    var array: [Tree?]?
}

The language does not require forward declarations.

Functions

Users can declare functions, each function must have a unique name.

Functions cannot be overloaded. Functions are not closures.

Functions can accept one or more arguments and may optionally return a result.

The func keyword introduces a function declaration.

Examples:

func fib(n: Int)->Int {
    var f1=1
    var f2=1
    var i=n
    while( i>1 ){
        var temp = f1+f2
        f1=f2
        f2=temp
        i=i-1
    }
    return f2
}

func foo()->Int {
    return fib(10)
}

Literals

The only literals are integer values and null.

Variables and Fields

The var keyword is used to introduce a new variable in the current lexical scope, or to add a field to a struct.

There are two forms of this:

When introducing variables, you can supply an initializer; this removes the need to specify a type. Examples:

var i = 1
var j = foo()

In this form the type of the variable is inferred from the initializer’s type.

The second form is more suited when declaring fields in a struct. In this form a type is required - initializer cannot be set.

Example:

struct T
{
    var f: Int
    var arry: [Int]
}

Creating new instances of Arrays

The new keyword is used to create array instances.

It must be followed by an array type name, and optionally followed by an initializer.

The array initializer must be a comma separated list of values, enclosed in { and }.

The array is sized based on number of values in the initilizer.

Alternatively the array initializer may have a field named len that specifies the size of the array, and a field named value to specify the value to use.

Examples:

var arry = new [Int] {1,2,3}
var arry2 = new [Int] {len=10, value=0}

The second example creates an array with 10 elements and sets the initial value to 0.

Creating new instances of structs

The new keyword is used to create struct instances.

It must be followed by the struct type name, and optionally followed by an initializer.

The struct initializer must be a comma separated list of field initializers, enclosed in { and }.

A field initializer has the form of name followed by = followed by an expression.

Examples:

var stats = new Stats { age=10, height=100 }

Control Flow

The language is block structured.

A block is enclosed in { and } and introduces a lexical scope.

The if statement allows branching based on a condition. The condition must be an integer expression; a value of zero is false, any other value is true.

The if statement can have an optional else branch.

The only looping construct is the while statement; this executes the sub statement as long as the supplied condition evaluates to a non zero value.

The break statement exits a loop.

The continue statement branches to the beginning of the loop.

The return statement takes an expression if the function is meant to return a value. It causes the currently executing function to terminate.

Expressions

Following table describes the available operators by their precedence (low to high):

Operator

Meaning

Type

||

logical or

Binary

&&

logical and

Binary

== != < <= > >=

relational

Binary

+ -

addition

Binary

* /

multiplication

Binary

- !

negate

Unary

(...), [], . ID

function call, array index, field access

Postfix

Grammar

The following grammar describes the language syntax:

program
    : declaration+ EOF
    ;

declaration
    : structDeclaration
    | functionDeclaration
    ;

structDeclaration
    : 'struct' IDENTIFIER '{' fields '}'
    ;

fields
    : varDeclaration+
    ;

varDeclaration
    : 'var' IDENTIFIER ':' typeName ';'?
    ;

typeName
    : nominalType
    | arrayType
    ;

nominalType
    : 'Int'
    | IDENTIFIER ('?')?
    ;

arrayType
    : '[' nominalType ']' ('?')?
    ;

functionDeclaration
    : 'func' IDENTIFIER '(' parameters? ')' ('->' typeName)? block
    ;

parameters
    : parameter (',' parameter)*
    ;

parameter
    : IDENTIFIER ':' typeName
    ;

block
    : '{' statement* '}'
    ;

statement
    : 'if' '(' expression ')' statement
    | 'if' '(' expression ')' statement 'else' statement
    | 'while' '(' expression ')' statement
    | postfixExpression '=' expression ';'?
    | block
    | 'break' ';'?
    | 'continue' ';'?
    | varDeclaration
    | 'var' IDENTIFIER '=' expression ';'?
    | 'return' orExpression? ';'?
    | expression ';'?
    ;

expression
    : orExpression
    ;

orExpression
    : andExpression ('||' andExpression)*
    ;

andExpression
    : relationalExpression ('&&' relationalExpression)*
    ;

relationalExpression
    : additionExpression (('==' | '!='| '>'| '<'| '>='| '<=') additionExpression)*
    ;

additionExpression
    : multiplicationExpression (('+' | '-') multiplicationExpression)*
    ;

multiplicationExpression
    : unaryExpression (('*' | '/' ) unaryExpression)*
    ;

unaryExpression
    : ('-' | '!') unaryExpression
    | postfixExpression
    ;

postfixExpression
    : primaryExpression (indexExpression | callExpression | fieldExpression)*
    ;

indexExpression
    : '[' orExpression ']'
    ;

callExpression
    : '(' arguments? ')'
    ;

arguments
    : orExpression (',' orExpression)*
    ;

fieldExpression
    : '.' IDENTIFIER
    ;

primaryExpression
    : INTEGER_LITERAL
    | IDENTIFIER
    | '(' orExpression ')'
    | 'new' typeName initExpression
    ;

initExpression
    : '{' initializers? '}'
    ;

initializers
    : initializer (',' initializer)*
    ;

initializer
    : (IDENTIFIER '=')? orExpression
    ;