The Ix Programming Language
Draft version 0.1

Daniel Bradley

www.crossadaptive.com

Created: 1 March 2012

Last updated: 28 March 2012

Introduction

Ix is primarily intended as a meta programming language -- software written in Ix will be compiled into intermediate languages by the Ix Translator, which in turn can be compiled with their corresponding compiler. Like other programming languages that have also used this technique, the first target language will be the C programming language.

Ix is conceptually similar to (early) Java, where each source code file represents either an interface or class definition. Unlike Java, inner and anonymous classes are not supported. Thus, Ix is intended to provide only a subset of the capabilities of its target languages, however as special features may be implemented during translation Ix will provide additional capabilities.

Each interface, class, and method, definition must be prefixed by a scope modifier that indicates who the object is 'visible' to. There are four modifier keywords: public, visible to all methods; protected, visible to all methods within the current namespace and child namespaces; hidden, visible to methods of subclasses; and private, only visible to methods of that class.

While the protected modifier is similar to having no modifier in Java, it should be noted that the Ix hidden modifier does not also imply protected access as it does in Java. The hidden modifier is intended to be used to restrict methods that only should be used internally within an object, and therefore should also be available to subclasses.

The rest of this document will informally describe the language.

I will use RFC-style MUST [NOT] and SHOULD [NOT] to describe what MUST be considered necessary and what SHOULD be considered necessary. I will use MAY [NOT] to describe optional elements.

Overview of language elements

Copyright

Each file MAY contain a copyright designator. There MAY be more than one copyright line.

The copyright keyword may be capitalised, e.g. "Copyright".

Copyright 2012 John Doe. All rights reserved.
Copyright 2012 Doe Industries.

The format is the copyright keyword, whitespace, then a free text identifier of the individual or company that copyright is assigned to terminated by a period ('.'), then free text terminated by a newline.

While it is envisaged that this will be converted into a comment header in target languages its presence as part of the language allows the possibility of programmatic handling of copyright issues. A potential example is determining whether software has been "tainted" by a non-employee in a company, or alternatively the percentage of software owned by an individual.

Author

Each file MAY contain an author designator. There MAY be more than one author line.

The author keyword MAY be capitalised, e.g. "Author".

Author: 2012, John Doe

The format is the author keyword, a colon, a year, then a comma, then a name terminated by a newline character.

Similar to copyright, it is envisaged that author lines will be embedded within a comment.

License

Each file MAY contain a license designator. There MAY be more than one license line.

The license keyword may be capitalised, e.g. "License". Also, the alternate spelling licence is also supported.

License: GPLv3, the General Purpose License version 3 or later.

The format is: the license keyword, a colon (':'), whitespace, then a free text identifier of the license that is terminated by a punctuation symbol such as comma (','), semi-colon (';'), or period ('.'), then the remainder of the line may be free text.

Similar to copyright, it is envisaged that the license will be embedded in a comment. Similarly, it is also envisaged that having a license statement will allow software to be automatically checked for License compliance.

Simple statements

Simple statements contain various arrangements of keywords and other words and symbols. Each simple statement begins with a word, letter, or the special character ('@'); and ends with a terminating semi-colon (';'). The following are examples of simple statements.

Namespaces

Each file, regardless of whether it contains an interface or class, MUST be placed within a namespace. This is done by using the namespace keyword, whitespace, then an identifier, then a semi-colon. Unlike other languages the identifier can contain the hyphen character, however, it should be understood that this may be converted to other characters (such as underlines '_') when the code is eventually generated.

Each file MUST only contain one namespace statement and it MUST be positioned below any copyright or license statements and MUST be above all other statements.

namespace some.dot.or-hypen-separated.identifier;

Generally, the identifier may be any valid Internet domain name. Similar to Java, a reverse order domain name associated with the developer(s) SHOULD be used. For example:

namespace org.ixlang;

Developers SHOULD NOT use a reverse order domain unless given permission by the registered owner of that domain, or when acting as an agent of the owner of that domain.

Loading external dependencies

Depending on the target language it may be desirable to limit the external dependencies that are loaded and considered by the compiler. Specifically, for C-style language targets it is desirable to limit the header files that are pre-processed by the compiler.

load org.ixlang*;
load openxds*;

The load keyword is followed by whitespace then by a scope identifier terminated by a semi-colon. The asterisk '*' acts as a terminating file-match wild card that will also match subdirectories that MUST only be used once in each load line.

If any load statements are specified they MUST be placed below the namespace statement and before any other statements.

Translation note

For the C-style programming language targets, load commands will be expanded into '#include' preprocessor directives.

Using namespaces

The use keyword MUST be used to specify the subset of namespaces that are search when resolving types.

use org.ixlang.*;

The use keyword is followed by namespace then either: a fully-qualified type, or a partial namespace or type identifier suffixed with the asterisk ('*') wildcard character.

If any use statements are used they MUST appear beneath any load statements and before any other statements.

Translation note

For C++ these would be expanded into appropriate 'using' directives, and for Java they would be expanded to appropriate 'import' directives. For targets such as C, or Objective-C, the statements would be used internally by the Ix Translator, but would not have a corresponding representation in the target programming language.

Interface statement

Interfaces are specified using the form: modifier keyword, the interface keyword, followed by the interface's name, then optionally the extends keyword and a list of interfaces, terminated by a semi-colon.

public interface MyInterface extends AnInterface, AnotherInterface;

Abstract method statements

Abstract method statements are used to define method signatures of interfaces and abstract classes that must be implemented by extending (or implementing) classes.

public square( a : integer, b : integer ) : integer;

The form is modifier keyword, then the name of the method, then a parameters list, then optionally the const keyword, then optionally a colon (':') followed by a return type, then a terminating semi-colon.

Blocks

A block is said to open when a left facing "opening" curly brace ('{') is encountered and closes when a right-facing "closing" curly brace ('}') is encountered. The purpose of a block is to contain other statements.

Blocks are used as the terminator of complex statements, however they are also commonly used to limit the scope of variables declared within methods. The smallest possible block is shown below.

{}

Complex statements

Complex statements begin with specific keywords then are terminated with a block.

Examples of complex statements include: class statements; method statements; enumeration statements; and control-flow statements such as loops and if-else.

Class statements

The class statement is used to name a class and define what attributes (member variables) it contains.

hidden class Coordinates
{
	@x     : integer;
	@y     : integer;

	#count : integer;
}

Unlike Java, but like Objective-C, the class statement's block only contain statements that define the class's attributes. Each attribute begins with the at symbol ('@') then the name of the attribute. The form of attribute statements will be described later in more detail. Class side attributes are indicated using a hash (or pound) symbol ('#').

public class Square extends Shape implements Area, Height, Width
{
	@width  : integer;
	@height : integer;
}

The form is: a modifier keyword, followed by the class keyword, followed by the class name, optionally followed by the extends keyword followed by the extended class's name, optionally followed by the implements keyword followed by a list of implemented interfaces, then terminated with the class attributes block.

The class statement MUST appear after any use statements, and MUST appear before any method, or abstract method, statements.

Translation note:

Class members are initialise to zeroed to null when classes are instantiated. When classes are deleted non-null pointer attributes are automatically deleted in reverse order.

Method statements

Method statements are real work-horses of object-oriented software. Their purpose is to manipulate the values of their object's attributes, or aid other methods to do so.

public static multiply( a : integer, b : integer ) : integer
{
	return a * b;
}

The form of the method statement is: a modifier keyword, then optionally the static keyword, then the name of the method, then a parameter list, then optionally the const keyword, then optionally a colon (':') and a return type, then is terminated by a method block.

Constructors

Constructors are defined using the new keyword for their name, with no specified return type.

public new( width : float, height : float )
{
	super( width );
	@width  = width;
	@height = height;
}

If a superclass constructor should be called the super method should be called before any class attributes are accessed. Otherwise, if it exists, the default parent constructor is called before the constructor runs.

Destructors

Ix classes automatically delete any non-null attributes that have a pointer type. However, if an explicit destructor is desired the delete method may be defined. This may be desirable to modify any housekeeping class-side attributes.

Once the custom delete has finished, the default delete method will delete any non-null objects.

public delete()
{
	//	Custom destructor.
}

For loop statement

The for loop statement is the same as those in C-style languages.

for ( integer i=0; i < len; i++ )
{

}

While loop statement

The while loop statement is the same as those in C-style languages.

while ( a_boolean_expression )
{
	do_something();
}

Foreach

There are two forms of the foreach loop:

foreach ( variable in an_array )
{
	do_something_to( variable );
} 

Alternate form, for PHP programmers.

foreach ( an_array as variable )
{
	do_something_to( variable );
} 

If statement

The if statement is the same as those in C-style languages.

if ( x < 10 )
{
	do_something_to( x );
}
else if ( x == 10 )
{
	do_something_else( x );
}
else
{
	do_something_else( x );
}

The if statement also has a simple statement form. Unlike other languages, this form does not support else.

if ( x == 10 ) do_something_to( x );

The form is: the if keyword, followed by a boolean expression, followed by a simple statement.

Switch statement

The switch statement is the same as those in C-style languages.

switch ( character )
{
case ' ':
case '\t':
case '\n':
	return Character#WHITESPACE;
default:
	return Character#TEXT;
}

The value provided MAY be either a primitive type; or alternatively MAY be an array, or class instance. When compiled to the target programming language, if native switch statements do not support more complex types, then the statement is programmatically converted into an if statement, and the contentEquals method is used to test equality.

switch ( ch )
{
case '\n':
case '\t':
case ' ':
	doWhitespace();
	break;

case 'A':
	doA();
	fallthrough;

default:
	doDefault();
}

Ix switch statements require that a fall-through from a previous switch statement be specified using the fallthrough keyword.

Keywords

Ix uses the following keywords: author; case; class; copyright; delete; fallthrough; for; foreach; hidden; if; license (and licence); load; namespace; new; private; protected; public; super; switch; use; and while.

Unlike most other languages file level keywords -- author, class, copyright, interface, license, load, namespace, and static; as well as the modifier keywords -- public, hidden, protected, and private; can be used as ordinary identifiers within blocks.

Primitive types

Ix has a restricted set of primitive types: boolean; character; float; and integer.

The character type represents UTF8 character values. The number types float and integer are signed and are represented using the most efficient type of the target machine architecture.

Block enclosed statements

The following statements can only exist within a block: attribute statements; expressions; and declaration statements.

Attribute statements

Attribute statements are contained within the attribute block of a class statement. These statements declare class instance variables that are accessible, and modifiable, from any method of the class.

@attribute_name : AttributeType*;

The form of an attribute is: the 'at' ('@') character followed immediately by the attribute name, followed by a colon, then followed by the attribute's class, enumeration, or interface, type, then terminated by a semi-colon (';').

An attribute type may be either a primitive type; a pointer, indicated by a suffixed asterisk ('*'); or a reference, indicated by a suffixed ampersand ('&').

@aPrimitive : integer;
@aPointer   : StringBuffer*;
@aReference : Tree&;

Declaration statements

Declaration statements are contained within method blocks and declare method variables that are visible within the correspond block scope. Unlike other statements, declaration statements have the type indicated first.

float area = 0.0;

The form is: variable type, then variable name, then optionally an assigning operator, then an expression (that returns a value of the nominated variable type), then a terminating semi-colon (';').

The following example shows an example of a volume method.

public calculateVolume( width : float, height : float, depth : float ) : float
{
	float area = calculateArea( width * height );

	return area * depth;
}

Expressions

Expressions are quasi-mathematical evaluations that return a single value or object. An expression is composed of literals (numbers, characters, or strings), variables, and operators. Like mathematical expressions, brackets are used to indicate precedence of evaluation.

z - (x * y)

Within expressions attributes are still prefixed by the 'at' symbol ('@').

Method resolution

Methods are resolved using the period character ('.') regardless of whether the type is a pointer or a reference.

Square* square = new Square();
float   area   = square.calculateArea();

return area;

Passing variables

Class attributes may only be passed into methods that accept references:

public calculateVolume( area : Area&, depth : float ) : float
{
	float area = area.calculateArea();
	return area * depth;
}
@area = new Square( 10.0, 20.0 );

float volume = calculateVolume( @area, 30.0 );

If a pointer is passed as a pointer through a method accepting pointers, the value of the original pointer becomes null.

StringBuffer* sb     = new StringBuffer();
String*       string = new String( "A tale of two cities" );

sb.append( string );

In the example above, after "string" is passed into the append method the value of string becomes NULL.

Assignment of pointers

When a pointer is assigned to another non-null variable, the existing object is deleted, the value of the source variable is copied to the target variable, and the source variable becomes NULL.

Number* a = new Number( 10.0 );
Number* b = new Number( 20.0 );

a = b;

The result of the above example is that a points to the Number storing 20.0, and b is NULL, while the Number storing 10.0 has been deallocated.

If a pointer is passed to a method as a pointer parameter, the original pointer becomes NULL.

Swapping pointers

Ix provides a special infix operator "<=>" that swaps the value of two variables.

Number* a = new Number( 10.0 );
Number* b = new Number( 20.0 );

a <=> b;

Deletion of pointers

Number* a = new Number( 10.0 );
...
delete a;

It follows that when an object is deleted, as it is passed to the delete operator, which takes a pointer for its argument, the variable (or class member attribute) is set to NULL.

Arrays

Arrays are a common programming language element. Arrays are specified by suffixing a type with square brackets.

integer[] values;

Ix arrays may either be declared as local with a constant size or as dynamically extendable.

Constant sized array

Constant sized arrays are declared by indicating an integer size with the brackets after the type.

integer[100] values;

Dynamically sized array

Dynamically sized arrays are declared by suffixing brackets after the type. The array is instantiated by using the new keyword.

integer[] values = new integer[];

An initial size may be specified by including an integer size.

integer[] values = new integer[1000];

Arrays may be dynamically added to in the same way as with PHP. The following statement will increase the array by one and store 10 in the last position.

values[] = 10;

Alternatively, a specific array position may be indicated. The array will be resized to accommodate the new position.

values[1001] = 10;

If a non-existant array position is accessed the NULL value is returned.

integer value = values[100];

Array pseudo-members

All arrays support the pseudo-members length, size, and capacity. For constant size arrays these are guaranteed to be equal. For dynamic arrays the capacity will be nearest power of 2 after the length. The size and length are always guaranteed to be equal.

integer[] values = new integer[50];

assert( 50 == values.length   );
assert( 64 == values.capacity );

Declaring array parameters

Constantly sized arrays can only be passed as references, while dynamically sized arrays may be passed as either pointers or references.

If a pointer should be passed an asterisk ('*') MUST be suffixed to the type.

public sum( values : integer[]* ) : integer
{
	integer sum;

	foreach ( value in values )
	{
		sum += value;
	}
	delete values;

	return sum;
}

If a reference should be passed an ampersand ('&') MUST be suffixed to the type.

public sum( values : integer[]& ) : integer
{
	integer sum;

	foreach ( value in values )
	{
		sum += value;
	}

	return sum;
}

The string type

The string type is a specialisation of a character array that is immutable, i.e. it is a short-hand for 'const character[]'. Like with arrays, individual characters may be retrieved through indexation.

Examples

The following example shows the implementation of a StringBuffer class.

Copyright 2012, Daniel Bradley. All rights reserved.

License: LGPLv2.

namespace org.ixlang.examples;

public class StringBuffer
{
	@data : char[];
}

public new()
{
	@data = new char[];
}

public append( ch : char )
{
	@data[] = ch;
}

public append( aString : string& )
{
	foreach( character in aString )
	{
		@data[] = character;
	}
}

public append( aString : string* )
{
	foreach( character in aString )
	{
		@data[] = character;
	}
	delete aString;
}

public append( aValue : float )
{
	this.append( Number.convertToString( aValue ) );
}

public const charAt( i : integer ) : char
{
	return @data[i];
}

public const getLength() : integer
{
	return @data.length;
}