Elan Language Reference
Links to documents: Elan Index and Symbols and Elan Library Reference

Comments

A comment is not an instruction: it is ignored by the compiler and does not change how the program works. Rather, a comment contains information about the program, intended to be read by a person seeking to understand or modify the code.

Every comment starts with the symbol # (known as ‘hash’) followed by some text or a blank line. The text field in a comment may contain any text, except that it must not start with the open-square-bracket symbol - [. Comments may be inserted at the same level as a global, member, or statement instruction, by entering # from the new code selector.

Every Elan program has a single comment at the top of the file, which is generated by the system and cannot be edited or deleted by the user. This comment is known as the ‘ file-header’ and shows the version of Elan being run together

Global Instructions

Global instructions (also referred to simply as ‘globals’) are located directly within a code file. They are never indented from the left-hand edge, nor may they be located within other instructions. Three of the globals – main, function, and procedure – are described as ‘methods’ and these are defined by one or more statements within them. Four of the globals – record, class, abstract class, and interface – define data structures and these always contain members. The two remaining globals – constant, and enum – do not contain any further instructions.

When you navigate to a new code at the global level (not indented from the left-hand edge of the code pane), you will be shown the 'global selector' listing the set of globals that you may insert there, namely all or only some of:

main procedure function test constant enum record class abstract interface #

where abstract is, in this context, short for abstract class. new code

The specific globals offered will depend upon your user profile.

Main

A program file must have a main method, sometimes called its main routine, if it is intended to be run as a program. You may, however, develop and test code that does not have a main method, either as a coding exercise or for subsequent use within another program.

The main defines the start point when a program is run.

The main does not have to be at the top of the file, but this is a good convention to follow.

The main may delegate work to one or more procedure or function.

There may not be more than one main in a file – and the global selector will not show the main option when one already exists in the file.

Example of a main method:
+main variable liname set to [3, 6, 1, 0, 99, 4, 67]expression call inPlaceRippleSortprocedureName(liarguments) print liexpression end main

Procedure

A procedure is a named piece of code that can define parameters which are given inputs via arguments in a call statement. Unlike a function, a procedure does not return a value. Also unlike a function, a procedure can have ‘side effects’: indeed it should have side effects, otherwise there would be no point in calling it! For this reason the statements within a procedure can:

+procedure inPlaceRippleSortname(out arr as List<of Int>parameter definitions) variable changesname set to trueexpression variable lastCompname set to arr.length() - 2expression +repeat set changesvariableName to falseexpression +for ivariableName from 0expression to lastCompexpression step 1expression +if arr[i] > arr[i + 1]condition then let tempname be arr[i]expression call arr.putAtprocedureName(i, arr[i + 1]arguments) call arr.putAtprocedureName(i + 1, temparguments) set changesvariableName to trueexpression end if end for set lastCompvariableName to lastComp - 1expression end repeat when not changescondition end procedure

Procedures are executed by means of a call statement, for example:

variable liname set to [3, 6, 1, 0, 99, 4, 67]expression call inPlaceRippleSortprocedureName(liarguments) print liexpression

Function

A function is a named piece of code that can define parameters which are given inputs via arguments when reference to the function occurs in a statement or expression. Unlike a procedure, a function returns a value. Also unlike a procedure, a function can have no ‘side effects’ and cannot depend on any System methods.

Example of a function:

+function scorename(g as Gameparameter definitions) returns IntType return g.body.length() - 2expression end function

Parameters

Parameters for both procedures and functions are defined in exactly the same way: each parameter definition takes the form:
  <name> as <Type>
for example: age as Intparameter definitions

Recursion

Procedures and functions may be called or referenced recursively. For example, a simple factorial calculation: +function factorialname(n as Intparameter definitions) returns IntType return (if n > 1 then n*factorial(n - 1)
else
1)
expression
end function

Test

A test is a set of assertions, at the global level, about the output of functions. Example of a test, from the binary search demo program:

+test optional description let li1name be ["lemon", "lime", "orange"]expression assert binarySearch(li1, "lemon")computed value is trueexpected value pass assert binarySearch(li1, "lime")computed value is trueexpected value pass assert binarySearch(li1, "orange")computed value is trueexpected value pass assert binarySearch(li1, "pear")computed value is falseexpected value pass let li2name be ["lemon", "orange"]expression assert binarySearch(li2, "lemon")computed value is trueexpected value pass assert binarySearch(li2, "orange")computed value is trueexpected value pass assert binarySearch(li2, "pear")computed value is falseexpected value pass let li3name be ["lemon"]expression assert binarySearch(li3, "lemon")computed value is trueexpected value pass assert binarySearch(li3, "lime")computed value is falseexpected value pass let li4name be empty List<of String>expression assert binarySearch(li4, "pear")computed value is falseexpected value pass end test

Notes

Testing Float values

When testing Float values it is recommend that you always use the round function to round the computed result to a fixed number of decimal places. This avoids rounding errors and is easier to read. For example:

+test round()optional description assert sqrt(2).round(3)computed value is 1.414expected value pass end test

Testing for runtime errors

If the expression you are testing causes a runtime error then the error will be displayed in the red fail message:

+test optional description let aname be [5, 1, 7]expression assert a[0]computed value is 5expected value pass assert a[2]computed value is 7expected value pass assert a[3]computed value is 0expected value actual: Out of range index: 3 size: 3 end test

If this occurs, mark the tests that you added since the last successful test run with ignore (see below), and then remove their ignore status one by one until the cause is identified and fixed.

Marking a test with ‘ignore’

It is possible to mark a test with the ignore keyword, by selecting the test frame and then hitting Ctrl-i, as in this example:

+ignore test clockTickoptional description let g1name be newGame(new Random())expression let g2name be newApple(g1)expression let g3name be clockTick(g2, "s")expression assert g3.headcomputed value is newSquare(22, 16)expected value not run end test

When a test is marked with ignore, that test will not be executed when the tests are run, and its result will be shown as ‘not run’. The overall test status will also show in the ‘warning’ status (amber colour), even if all the tests that did run passed. This is to discourage you from leaving a test marked ignore for long.

The principal reason for marking a test ignore is when either the test code, or code in any function being called, does not terminate. This typically means that there is a loop (or a recursive call) with no exit condition, or where the exit condition is never met.

If you do create such code without realising it, then when the tests are executed the test runner will ‘time out’ after a few seconds (most tests will pass in milliseconds), and an error message will be shown in the System info pane. The test that caused the timeout will automatically then be marked ignore. Your priority should then be to identify the cause of the timeout and attempt to fix it before then restoring the test by selecting its frame and hitting Ctrl-i {which is a toggle for setting and unsetting an ignore status).

Constant

A constant defines a named value that cannot change and is always defined at global level (directly within a file) and is global in scope.

Examples: +constant phiname set to 1.618literal value or data structure +constant maxHitsname set to 10literal value or data structure +constant warningMsgname set to "Limit reached"literal value or data structure +constant fruitname set to {"apple", "orange", "banana"}literal value or data structure +constant blackname set to 0x000000literal value or data structure +constant redname set to 0xff0000literal value or data structure +constant coloursname set to {Suit.spades:black, Suit.hearts:red, Suit.diamonds:red, Suit.clubs:black}literal value or data structure +constant scrabbleValuesname set to {"A":1, "B":3, "C":3, "D":2, "E":1, "F":4, "G":2, "H":4, "I":1, "J":8, "K":5, "L":1, "M":3, "N":1, "O":1, "P":3, "Q":10, "R":1, "S":1, "T":1, "U":1, "V":4, "W":4, "X":8, "Y":4, "Z":10}literal value or data structure

In the colours example above, Suit is an Enum.

Enum

An enum – short for ‘enumeration’ – is the simplest form of ‘user-defined Type’. It specifies a set of values, each of which is defined as a name, such that a named value of Type enum necessarily always holds one of those values.

enums are read-only: once they have been defined it is not possible to add, remove, or update their values.

Examples: +enum SuitName spades, hearts, diamonds, clubsvalues +enum ActionName stand, drawvalues +enum OutcomeName undecided, win, lose, draw, winDoublevalues +enum StatusName pending, playing, standing, blackjack, bustvalues

Type name

The name given to an enum (see below), which must begin with an upper case letter, is used as the Type name when passing a value to or from a procedure or function.

Using an enum

The value is specified by the Type name for the specified enum, followed by a dot and the value name, for example: variable xname set to Status.pendingexpression

Record

A record is a user-defined data structure that is given a Type name (which must begin with an upper case letter). The record defines one or more properties, each of which has a name (starting with a lower case letter) and a Type. The Type of a property may be any simple value Type, or a ListImmutable, or another Type of record ( or even the same Type of record).

Note that the Type record has some similarity to a class in that:

However a record differs from a class in that:

Examples: +record SquareName property xname as IntType property yname as IntType end record +record GameName property headname as SquareType property bodyname as ListImmutable<of Square>Type property priorTailname as SquareType property applename as SquareType property isOnname as BooleanType property rndname as RandomType property graphicsname as BlockGraphicsType property keyname as StringType end record Having defined a record Type, such as Game above, you can create as many instances as you wish using the following syntax to specify the values: let g1name be new Game() with
head set to newSquare(22, 15),
key set to "d",
isOn set to true
expression
Note that you are not required to provide a value for each property because, where a property is not specified in the ‘with clause’ (as above), that property will be given the empty (default) value of the correct Type.

You can then read the values from the properties using ‘dot syntax’ for example:

print sq.sizeexpression

record Types are immutable: the properties on an instance may not be changed, directly. However, you can easily create another instance that is a copy of the original, with all the same property values except for any specific changes made in another with clause. The newly-minted copy (with changes) must be assigned to a new named value. For example:

let sq1name be new Square() with
x set to 3.5,
y set to 4.0,
size set to 1.0
expression
let sq2name be copy sq1 with
size set to 2.0,
colour set to red
expression
Or even to the same name if that name is a variable:

variable aname set to new Square() with
x set to 3.5,
y set to 4.0
expression
set avariableName to copy a with
x set to 3.7
expression

This last example shows how you enter the comma-separated with clauses. The earlier examples show how the Editor displays a set of with clauses.

If you want to use one or more existing property values in order to determine a new value, the property names must be prefixed with the name of the instance being copied, for example:

let sq2name be copy sq1 with
size set to sq1.size + 3
expression

Record deconstruction

A record may be ‘deconstructed’, meaning that its properties are read into separate variables using the same syntax as for deconstructing a Tuple. For example, assuming that Square is a record defined as in the example above, then this code:

let x, y, size, colourname be mySquareexpression

will read the properties into the four names defined.

When deconstructing, the names of the values must match the names of the properties of the record. However, the ordering of the names does not have to match the order in which the properties are defined in the record.

Class

See also: Inheritance

A class is a user-defined Type offering far richer capability than an enum.

Note that a record is in some ways similar to a class but simpler: it defines properties, but has no constructor and no methods. See Working with records.

Definition

Here is an example of class definition, taken from the Snake OOP demo program:

+class AppleName inherits ClassName(s) property locationname as SquareType +procedure newRandomPositionname(snake as Snakeparameter definitions) +repeat let ranXname be randomInt(0, 39)expression let ranYname be randomInt(0, 29)expression set property.locationvariableName to new Square(ranX, ranY)expression end repeat when not snake.bodyCovers(property.location)condition end procedure +function updateGraphicsname(gr as BlockGraphicsparameter definitions) returns BlockGraphicsType return property.location.updateGraphics(gr, red)expression end function end class

A class must have a name that, like any other Type, begins with an upper case letter.

A class may define:

Using a class

A class is instantiated using the keyword new followed by the class name and brackets, which should enclose the comma-separated arguments required to match the parameters (if any) defined on the constructor for that class. For example (also from the Snake OOP demo):

+constructor(parameter definitionsparameter definitions) let tailname be new Square(20, 15)expression set property.currentDirvariableName to Direction.rightexpression set property.bodyvariableName to [tail]expression set property.headvariableName to tail.getAdjacentSquare(property.currentDir)expression set property.priorTailvariableName to tailexpression end constructor

The created instance may then be used within expressions, like any other variable.

Inheritance

An ordinary class (also known as a ‘concrete class’) may optionally inherit from just one abstract class but may additionally inherit from any number of interfaces. The concrete class must define for itself a concrete implementation of every abstract member defined in the abstract class or any interfaces that it inherits from, directly or indirectly.

Notes:

Abstract Class

See also: Inheritance

An abstract class may not be instantiated (and hence may not define a constructor). It may define concrete members i.e.:

As with a concrete class, any of these members may be made private, after the corresponding frame has been added, by selecting that member frame and pressing Ctrl-p.

These concrete members are automatically inherited by any subclass, but they may not be overridden (re-defined) by the subclass. Therefore you should define concrete members only if they are intended to work identically on every subclass.

You may also define abstract methods on an abstract class, i.e. abstract property, abstract function, abstract procedure. Such methods define only the signature of the method, not the implementation (body), therefore they have no end statement. For example:

abstract function calculateDiscount() as Float

If you wish to have several subclasses of an abstract class that share a common implementation for a method, but require that some of the subclasses can define a different implementation, then you should:

Interface

See also: Inheritance

An interface is similar to an abstract class, with the difference that it may define only abstract members. The advantage of using an interface instead of an abstract class is that a concrete class can inherit from multiple interfaces.

An interface may inherit only from other interfaces.

Important: An interface must not re-declare abstract interfaces that are defined in any interface it inherits from, directly or indirectly.

# (comment)

See Comments.

Member Instructions

Member instructions (also referred to simply as ‘members’) are located directly within the a: record, class, abstract class, interface. All members, with the exception of property, define one or more statements within them.

When you navigate to a new code that is at member level (located directly within a record, class, abstract class, or interface) you will be shown the set of statements that may be inserted there, for example:

new code

The specific members offered will depend upon the context, and/or upon your user profile. The full set of entries is shown here, with links to explanations below:

constructor, property, procedure, function, abstract property, abstract procedure, abstract function, private property, private procedure, private function, #

Constructor

A concrete class may define a single constructor, which may:

If a class does define a constructor, and the constructor defines any parameters, then when the class is instantiated (using new) then values of the correct types must be provided, for example, if the class Square defines this constructor:

+constructor(x as Int, y as Intparameter definitions) set property.xvariableName to xexpression set property.yvariableName to yexpression end constructor

then it may be instantiated like this:

let tailname be new Square(20, 15)expression

Property

Examples:

property height as Int
property board as Board
property head as Square
property body as [Square]

If the property is not initialised within the constructor then it will automatically be given the empty value for that Type. You may test whether a property contains this default value by writing e.g.:
if head is empty Square

constructor(board as Board)
set property.board to board
end constructor

procedure setHeight(height as Int)
set property.height to height
end procedure

Procedure Method

A ‘procedure method’ follows the same syntax and rules as a global procedure. The differences are:

Function Method

A function method follows the same syntax and rules as a global function>. The differences are:

Abstract Property

An abstract property may be defined only on an abstract class. Any concrete sub-class must then implement a concrete (regular) property to match.

Abstract Procedure Method

An abstract procedure method may be defined only on an abstract class. Any concrete sub-class must then implement a concrete (regular) property to match.

Abstract Function Method

An abstract function method may be defined only on an abstract class. Any concrete sub-class must then implement a concrete (regular) property to match.

# (comment)

See Comments.

Statement Instructions

Statement instructions (also referred to simply as ‘statements’) are located within ‘methods’. Some of these statements may contain other statements.

When you navigate to a new code that is at statement level (located within a global or a member instruction) you will be shown the set of statements that may be inserted there, for example:

new code

The specific statements offered will depend upon the context, and/or upon your user profile. The full set of entries is shown here, with links to explanations below:

assert call each else for if let print repeat set throw try variable while #

Assert statement

Procedure call

Each loop

Else clause

For loop

The loop counter variable does not have to have been defined in a variable statement.

The three defining values (from, to, and step) must all be integer, positive or negative.

They may be defined by literal integers, variables of Type Int, or expressions that evaluate to an integer.

However, if you require a negative step then the literal value, variable, or expression must start with a negative sign as this is needed at compile time to determine the nature of the exit condition. So if you have a variable s that holds a negative value to be used as the step, then you will need to write something like the following:

variable s set to -3
for i from 100 to 0 step -(-s)
  ..
end for

If statement

See also if expression

Example 1: +if head is applecondition then call setAppleToRandomPositionprocedureName(apple, bodyarguments) 'setAppleToRandomPosition' is not defined else if call body.removeAtprocedureName(0arguments) end if Example 2:
if item is value then
  set result to true
else if item.isBefore(value) then
  set result to binarySearch(list[..mid], item)
else
  set result to binarySearch(list[mid + 1..], item)
end if
Notes:

Let statement

The let statement may be thought of as being between a constant and a variable. Like a variable a let may be defined only within a routine, but unlike a variable it may not be re-assigned once defined. It is recommended that you always use a let in preference to a variable unless you need to be able to assign a new value to it.

Print statement

The simplest way to print is with the print statement. For example: print "Hello"expression let aname be 3expression let bname be 4expression print a*bexpression print "{a} times {b} equals {a*b}"expression Note:

Repeat loop

Set statement

The set statement is used to assign a new value to an existing variable. The new value must be of the same Type as (or a Type compatible with) that of the variable. A set statement may not assign a new value to a parameter within a procedure.

Throw statement

If you are in mode Advanced level coding (see Preferences), you can deliberately generate, or ‘throw’, an exception when a specific circumstance is identified, using a throw statement, for example:

throw exception "something has happened"

Try statement

If you are in mode Advanced level coding (see Preferences), you can test whether another piece of code might throw an exception by wrapping it in a try statement. This might arise when calling a System method that is dependent upon external conditions, for example: +try call fooprocedureName() print "not caught"expression +catch exception in evariableName print eexpression end try The variable holding the exception (by default named e, but this may be changed by you) is of Type String. You can compare the exception message to one or more expected messages and, if the message does not match an expected exception, you may choose to throw the exception 'up', as in this example: +try call fooprocedureName() print "not caught"expression +catch exception in evariableName +if e isnt "an expected message"condition then throw exception emessage end if end try

Variable statement

While loop

# (comment)

See Comments.

Expressions

One of the most important constructs in programming is the ‘expression’. An expression is evaluated to return a value. An expression is made up of the following possible elements:

which this chapter describes.

Literal value

A literal value is where a value is written ‘literally’ in the code, such as 3.142 – in contrast to a value that is referred to by a name.

The following data Types may be written as literal values (follow the links to view the form of each literal value):

Int, Float, Boolean, String, List, ListImmutable, Dictionary, DictionaryImmutable, Tuple

Named value

A named value is a value that is associated with a name rather than being defined literally in code. There are various kinds of named value:

Constant, let statement, variable statement, Parameter passing, enum statement. Once a named value has been defined, it can be referred to by the name.

Identifier

For all kinds of named values, the name must follow the rules for an ‘identifier’. It must start with a lower case letter, followed by any combination of lower case and upper case letters, numeric digits, and the _ (underscore) symbol. It may not contain spaces or other symbols.

Scoping and name qualification

With the exception of a constant (below), which is global in scope, named values are always ‘local’: their scope is confined to the method in which they are defined.

Elan allows local named values to be defined with the same name as a constant, function, or procedure defined at global level or defined in the standard library. In such cases, when the name is used within the same method, then it will refer to the local definition. If you have done this, but then need to access the constant, function, or procedure with the same name, then you can simply prefix the use of the name with a ‘qualifier’ of either global. or library. as appropriate.

Indexed Value

If a variable is of an indexable Type, then an index or index range may be applied to the variable within an expression. For example:
    variable a set to "Hello World!"
    print a[4]o
    print a[4..]o World!
    print a[..7]Hello W   (since the upper bound of a range is exclusive)
    print a[0..4]Hell         (for the same reason)

See also: Using an List, Using a Dictionary

Important: unlike in many languages, indexes in Elan (whether, single, multiple, or a range) are only ever used for reading values. Writing a value to a specific index location is done through a method such as in these examples:

    putAt            on a   List
    withPut        on a    ListImmutable
    putAtKey      on a    Dictionary
    withPutKey  on a    DictionaryImmutable

Operators

Arithmetic operators

Arithmetic operators can be applied to Float or Int arguments. The result may be a Float or an Int depending on the arguments.

For ^ + - *, the result is a Float if either of the arguments is a Float, and an Int if both arguments are Int.

For /, the result is always a Float. It can be converted to an Int using the floor() function.

mod and div only operate on Int arguments, and the result is Int.

    2^38
    2/30.666...
    2*36
    2 + 35
    2 - 3-1
    11 mod 32 (integer remainder)
    11 div 33 (integer division)

Arithmetic operators follow the conventional rules for precedence i.e. ‘BIDMAS’ (or ‘BODMAS’).

When combining div or mod with any other operators within an expression, insert brackets to avoid ambiguity e.g.:
    (5 + 6) mod 3

Note that mod is more of a remainder operator than a modulus operator -- the result takes the sign of the first argument. If both arguments are positive, there is no difference.

The minus sign may also be used as a unary operator, and this takes precedence over binary operators so:
    2*-3-6

Note that the Elan editor automatically puts spaces around the + and binary operators, but not around ^, / or *. This is just to visually reinforce the precedence.

Logical operators

Logical operators are applied to Boolean arguments and return a Boolean result.

and and or are binary operators
not is a unary operator.

The operator precedence is notandor, so this example, which implements an ‘exclusive or’, need not use brackets and can rely on the operator precedence:

+function xorname(a as Boolean, b as Booleanparameter definitions) returns BooleanType return a and not b or b and not aexpression end function

Equality testing

Equality testing uses the is and isnt keywords with two arguments. The arguments may be of any Type.

Numeric comparison

The numeric comparison operators are:

    >         for     greater than
    <         for     less than
    >=        for     greater than or equal to
    <=        for     less than or equal to

Each is applied to two arguments of Type Float, but any variable or expression that evaluates to an Int may always be used where a Float is expected.

Notes:

Combining operators

You can combine operators of different kinds, e.g. combining numeric comparison with logical operators in a single expression. However the rules of precedence between operators of different kinds are complex. It is strongly recommend that you always use brackets to disambiguate such expressions, for example:

(a > b) and (b < c)expression
(a + b) > (c - d)expression

Function call

An expression may simply be a function call, or it may include one or more function calls within it. Examples: print sinDeg(30)expression variable xname set to sinDeg(30)^2 + cosDeg(30)^2expression variable namename set to inputString("Your name")expression print name.upperCase()expression Notes:

Lambda

A lambda is a lightweight means to define a function ‘in line’. You typically define a lambda:

The syntax for a lambda is as follows:

Example: +function liveNeighboursname(cells as List<of Boolean>, c as Intparameter definitions) returns IntType let neighboursname be neighbourCells(c)expression let livename be neighbours.filter(lambda i as Int => cells[i])expression return live.length()expression end function Notes:

If expression

The ‘if expression’ is in certain respects similar to an If statement, but with the following differences:

Here are three examples:

return if c < 1160 then c + 40
else
c - 1160
expression

return if isGreen(attempt, target, n) then setChar(attempt, n, "*")
else
attempt
expression

return if attempt[n] is "*" then attempt
else
if isYellow(attempt, target, n) then setChar(attempt, n, "+")
else
setChar(attempt, n, "_")
expression

New instance

A 'new instance' expression is used to create a new instance of a library data structure, or a user-defined class or record - either to assign to a named value, or as part of a more complex expression. Example of use (taken from the `Snake - procedural` demo):

let blocksname be new Array2D<of Int>(40, 30, white)expression

Where the new instance is of a user-defined class or record the expression may optionally be followed by a 'with clause` in order to specify any property values. Example of this use:

+function dealCardname(random as Floatparameter definitions) returns CardType let numbername be (random*52).floor()expression let rankname be rankValue.keys()[number div 4]expression let suitname be number mod 4expression return new Card() with
rank set to rank,
suit set to suit
expression
end function

Copy with

A 'copy with' expression is used to make a copy of an existing instance, but with a different value for one or more of the properties - either to assign to a named value, or as part of a more complex expression.. It is used extensively within 'functional programming' where you are dealing withrecords or other immutable types. Example of use in this manner (taken from the 'Snake - functional' demo program): +function newApplename(g as Gameparameter definitions) returns GameType let x, rnd2name be g.rnd.nextInt(0, 39)expression let y, rnd3name be rnd2.nextInt(0, 29)expression let apple2name be newSquare(x, y)expression let g2name be copy g with
apple set to apple2,
rnd set to rnd3
expression
return if bodyOverlaps(g2, apple2) then newApple(g2)
else
g2
expression
end function

However, copy with may also be used in object-oriented programming, with instances of regular (mutable) classes. Also, note that a with clause (following the same syntax as in a copy with expression), may be used within a new instance expression to set up properties for the object not specified in the constructor.

Empty of Type

An 'empty of Type' expression is used to make the default (empty) instance of any Type - usually only created for comparing to another instance to test whether that other instance is also empty or default. This may arise, for example, if a class or record is defined with a property that has never had a value assigned to it. The following example is taken from the 'Pathfinder' demo program: +procedure visitNextPointname(parameter definitionsparameter definitions) call updateNeighboursprocedureName() set property.currentvariableName to nextNodeToVisit()expression +if (property.current is empty Node) or (property.current.point is property.destination)condition then set property.runningvariableName to falseexpression else if call property.current.setVisitedprocedureName(truearguments) end if end procedure

It is also possible explicitly to set a property or a named value to an empty instance of the appropriate Type.

Errors and warnings

Parse errors

A parse error occurs when the text entered into a field cannot be matched against any of the valid formats for contents of that fields. The field will display in red (for the default colour scheme) with a message alongside it containing a link to one of the headings below. The Parse status in the status panel in the IDE will show as Invalid.

Invalid argument list

An argument list passed into a function or procedure call, must consist of one or more arguments separated by commas. Each argument may in general be any of:

(though in certain very specific contexts some options are disallowed by the compiler.)

Invalid assert actual

The 'actual' field in an assert statement must be a named value or a function evaluation.

Invalid constant value

The value of a constant must be a literal value of a type that is not mutable. This can be a simple value (e.g. a number or string), or an immutable List or Dictionary.

Invalid enum value(s)

Enum values must each be a valid identifier, separated by commas.

Invalid exception message

An exception message must either be a literal string or a named value holding a string.

Invalid expression

The field you are entering must be a valid expression.

Invalid name

The name defined for any variable, constant, let, parameter, function, procedure, or property must follow the rules for an identifier.

Invalid inheritance clause

An inheritance clause, if used, must consist of the keyword inherits followed by a space and then one or more Type names separated by commas.

Invalid parameter list

Each parameter definition takes the form:

name as Type

The name must follow the rules for an identifier.

The type must follow the rules for an type.

If more than one parameter is defined, the definitions must be separated by commas.

Invalid reference to a procedure

Valid forms for a procedure call are

The last one is used only if there is a need to disambiguate between a library procedure and a user-defined (global) procedure with the same name.

Invalid Type format

For certain types the name may be followed by an of clause, for example:

List<of Int> Dictionary<of String, Int>

Invalid Type name

Type names always begin with a captial letter, optionally followed by letters or either case, numeric digits, or underscore symbols. Nothing else.

Invalid variable or let definition

The definition for a variable, or for a let statement is most commonly a simple name. Less commonly, it may take the form of: tuple deconstruction, list deconstruction, or record deconstruction.

Compile Errors and Warnings

Q: What is the difference between a compile error and a warning. A: A warning usually indicates that the fix may involved adding some more code, for example adding a definition for an unknown identifier. An error usually indicates that you will need to alter code to fix the error. But they are similar in that you will not be able to run your program until the issues are fixed. In all programming languages it is a good practice 'treat all compile warnings as errors' i.e. fix them as soon as you see them appear.

Expression must be ...

An expression, when evaluated, results in a value of a type that is not compatible with its 'target', for example: if the result of the expression is being assigned to an existing variable, or if an expression is defined 'inline' as an argument into a method call.

Cannot use 'this' outside class context

The keyword this may only be used within an instance method on a class to refer to the current instance.

Abstract Class ... must be declared before it is used

If a class inherits from one or more abstract classes, then the latter must all be already declared (defined) earlier in the code file.

Member ... must be of type ...

This error occurs when a class is defined as inheriting from an abstract class, # and has implemented an inherited member (method or property) with the correct name, but with different type(s).

Incompatible types. Expected: ... Provided: ...

Cannot determine common type between ... and ...

for type ...is not defined ...

Arises when 'dot-calling' a member (method or property) that does not exist on the type of the named value or expression before the dot.

Cannot call a function as a procedure

A function (or function-method) is to be used within an expression, not via a call instruction.

Cannot use a system method in a function

A 'system method' (defined in the Standard Library) returns a value like a function does. However, because a system method either makes changes to the system and/or depends on external inputs, it may be used only within a procedure or the main routine.

Code change required ...

Indicates that a library method or class has been changed since the version in which your Elan code was written. The link in the message should take you directly to information in in the Library Reference documentation on how to update your code to cope with the change.

Cannot call procedure ... within an expression

A procedure may only be used within a call instruction.

Cannot invoke ... as a method

The code is attempting to use a free-standing method (function or procedure) as a 'dotted-method' on a named value or the result of an expression.

Cannot ...index ...

An index (in square brackets) may be applied only to certain data structure types: String, Array, Array2D, List, ImmutableList, Dictionary, and ImmutableDictionary.

Cannot range ...

An range may be applied only to certain data structure types: String, Array, List, and ImmutableList.

Cannot new ...

The type specified after the call keyword cannot be instantiated. Either the type is unknown, or it is an abstract class, or an interface.

Source for 'each' must be an Array, List, or String.

Superclass ... must be inheritable class

A concrete class may inherit from an abstract class, and/or an interface, but not from another concrete class.

In Elan, all classes must be either abstract or 'final' - a final class being concrete and not-inheritable.

Superclass ... must be an interface

An interface may inherit from other interfaces, but not from any class.

Class/interface ... cannot inherit from itself

The message is self explanatory.

There must be only one abstract superclass ...

A class may inherit from only one abstract class. However, it may additionally inherit from one or more interfaces.

Cannot reference private member ...

A private member (method or property) may be accessed only by code within the class, or within sub-classes of it. It may not be accessed by any code outside the class hierarchy.

... must implement ...

If a concrete class inherits from any abstract class or interface(s) it must implement all abstract methods defined in those types.

... must be concrete to new

You cannot create an instance of any abstract class or interface - only of a concrete class.

Cannot pass ... as an out parameter

If a parameter of a procedure is marked with out then this means that the parameter may be reassigned within the procedure. Therefore you must pass in a variable that can be re-assigned. You cannot pass in: a constant, a literal value, or a named value that is defined by a let instruction.

Cannot call extension method directly

A method that is defined within the Library as an 'extension method', such as asString may only be called using 'dot-syntax' on a named value or an expression.

Cannot prefix function with 'property'

The prefix out. may only be used before a property name - not a function name.

Missing argument(s) ...

The method being called expects more arguments than have been provided.

Too many argument(s) ...

A method has been passed more arguments than it expects.

Argument types ...

One or more arguments provided to the method are of the wrong type.

<of Type(s)>

The the number of types given in the of clause, does not match the number required for the specified outer type.

May not re-assign the ...

Attempting to re-assign, or mutate, a named value that may not be re-assigned in the current context.

Name ... not unique in scope ...

Attempting to create an identifier with the same name as one already defined within the same scope.

May not set ... in a function

A property cannot be re-assigned only within a procedure method, not within a function - because re-assigning a property is a 'side effecr'.

The identifier ... is already used for a ... and cannot be re-defined here.

An existing named value may not be defined again within the same scope.

Duplicate Dictionary key(s)

Attempting to define a literal Dictionary or DictionaryImmutable with one or more duplicated keys in the definition.

To evaluate function ... add brackets ...

If you intend to evaluate a function, the function name must be followed by brackets even if the function defines no parameters. If your intent was to define a reference to the function (a pattern used commonly in 'Functional Programming' then the name of the function must be preceded by the keyword ref and a single space.

Library or class function ... cannot be preceded by by 'ref'

The keyword ref may be applied only to functions that you have defined in your own code as a standalone (global) function. It may not be applied to a function method defined on a class, nor to a library function. (You may, however, define your own function that simply delegates its implementation to a library function.

a comment may not start with [ unless it is a recognised compiler directive

Compiler directives are a planned future capability - they will look like comments, but begin with an open-square bracket. To avoid the possibility of ambiguity, you may not start your own comments with an open square bracket.

Condition of 'if' expression does not evaluate to a Boolean.

Cannot have any clause after unconditional 'else'.

... is a keyword, and may not be used as an identifier.

An Elan keyword cannot be used to as the name for a value, property, or method. Try shortening the name, lengthening it, or using a different name

... is a reserved word, and may not be used as an identifier.

In addition to keywords there are certain other 'reserved words' that cannot be used the name for a value, property, or method. Some of these are potential keywords that might be added to Elan in future releases. The reason you cannot use int, float, string, boolean, array, list, dictionary as identifiers is because it is not considered good practice to use the name of a type as an identifier even though (in Elan) the capitalisation is different. Instead you should give each identifier a name that indicates what it does (for a method), or represents (for a named value).

Index cannot be negative.

An index into an array or list cannot have a negative value. If a negative is given in literal form e.g. a[-3] then this will generate a compile error. If you use a named value for an index and it is negative, then this will cause a run-time error.

Cannot do equality operations on Procedures or Functions.

Property ... is not of an immutable type.

Properties on a record may only be of immutable types.

... cannot be of mutable type ...

Element type for a ListImmutable must itself be an immutable type. Similarly, for an DictionaryImmutable the types for both the key and the value must be immutable types.

... cannot have key of type ...

The type of the key for any dictionary Dictionary must be an immutable type, and not itself an indexable type.

No such property ... on record ...

The property name given in the record deconstruction does not match a property on the given type of record.

Cannot discard in record deconstruction ...

Wrong number of deconstructed variables.

referencing a property requires a prefix.

If you are referring to a property of a class from code defined within the class then the property name must be preceded by property.

'out' parameters are only supported on procedures.

You cannot defined an out parameter in a function (because that would imply the possibility of creating a side-effect).

There can only be one 'main' in a program.

Unsupported operation.

You cannot chain two 'unary' operators (those that apply to a single value) - such as - or not successively within an expression.


Elan Language Reference go to the top