The L language

L language reference manual in PDF format

Overview

What is L

The L programming language, along with its compiler, library and the simple web page scripting built on top of them, is a long-term hobby of mine. However it is not a toy programing language - it has the features you'd expect from a language in the vein of Java or C# and it compiles to either bitcode or optimized native code, via LLVM

Like any other programming language, L reflects the ideas of its author - in this case me - about what makes a good programming language. I've used a few different programming languages over the years, starting with ZX81 BASIC and Z80 machine code and then working my way though more BASIC, Pascal1, Modula-2, C, Ada, Smalltalk2, C++, Java, C# and VB.Net and I've created a number of languages of my own both for fun (such as a peculiar macro assembler written in BASICA for the IBM PC and an object orientated Forth) and occasionally for work (including a screen-scraping scripting language for the IBM 3270 PC and an interpreter for a simple application scripting language)

L takes the aspects I like from all languages I've used or toyed with. The result is an object orientated language with a strong and static type system. The syntax is influenced by Algol-68 and the object model is much like Java. The library relies extensively on templates, which are taken from C++ (although L's templates are much simpler than those in C++). All objects are stored on a garbage collected heap - life is too short for manual memory management - and finalizers and/or finally blocks can be used to dispose of anything that requires additional cleanup. The approach to run time checks and exceptions in general is to not allow anything dangerous but to allow checks to be bypassed without fuss by those determined to shoot themselves in the foot.

1,2Borland Turbo Pascal and Digitalk SmalltalkV were life altering products for the geek in me

Lexical structure

Characters

L supports only 7-bit ASCII source (this will probably change to UTF-8 in a future version)

White space

White space delimits some tokens but is otherwise ignored

Newlines

Newlines (cast char(10)) are treated as white space and are ignored except to terminate line comments. Carriage returns (cast char(13)) are not valid within an L program and will result in a parse error

Comments

L has two kinds of comments: line comments and C-style comments

Line comments

Line comments are introduced with // and are terminated by the next newline character. The contents of the comment are ignored

C-style comments

C-style comments are introduced with /* and are terminated by */. C-style comments do not nest

Keywords

bool, break, case, cast, catch, char, class, const, continue, default, do, elif, else, end, enum, esac, false, fi, finally, for, foreach, get, if, import, int, is, isa, long, namespace, native, new, null, od, pragma, private, proc, protected, ptr, public ref, return, set, static, struct, super, then, this, throw, true, try, use, var, void, while, word, yrt

Symbols

[] = && ! || & | ~ ^ : , + - / % * . { } ( ) [ ] ; == >= > <= < != =~ !~

Literals

Integer literals

Integer literals can be either base ten or base 16. Integer literals default to type int. A suffix of 'l' or 'L' makes a literal of type long and a suffix of 'w' or 'W' makes a literal of type word

 

integer_literal = decimal_literal | hex_literal

decimal_literal = ((['0'-'9']+)(['W' 'w' 'L' 'l' 'C' 'c'])?)

hex_literal = (('0'['x' 'X'](['A'-'F' 'a'-'f' '0'-'9'])+)(['W' 'w' 'L' 'l' 'C' 'c'])?)

String literals

Data types

Primitive data types

L has a small number of primitive types. There are no unsigned integer types and no floating point types at all

 

bool

Values of the bool type hold one of two values whose literals are true and false. The default initialize value for booleans is false.

 

bool b = false;

byte

byte type hold signed 8-bit integers. There are no literal values of this type and the default initialize value is cast byte(0)

byte b = cast byte(0x1F);

char

Values of the char type also hold signed 8-bit integers. Characters may become unsigned in a future version. Literal characters are enclosed in single quotes. The default initialize value is cast char(0)

char c = 'X';

int

Values of the int type hold signed 32-bit integers. Literals are decimal or hexadecimal integers in C syntax.

int i = 123;

long

Values of the long type hold signed 64-bit integers

long l = 200000000000L;

word

Values of the word type hold a signed integer of at least pointer width.

word w = cast word(apointer);

 

basetype :

'int'

| 'long'

| 'word'

| 'bool'

| 'char'

| 'byte'

| 'void'

Constructed types

Arrays

An array type is written "type[]"

int a, b; // both a and b have type int[]

Pointers

A pointer type is written "type ptr"

 

// declare a pointer to an integer

int ptr p;

 

// declare a pointer to a pointer to an array of integers

int[] ptr q;

 

Pointer arithmetic is not allowed in sandboxed mode.

References

A reference type is written as "type ref"

 

// declare a reference to an integer

int ref p;

 

Refence types may only appear in a method or proc argument list. The only way to initialize a variable of reference type is to pass a variable to a method expecting a reference parameter. At all other times references are automatically de-referenced

 

complex_type:

: type '[]'

| type 'ptr'

| type 'ref'

| generic_type

Type conversions

Implicit conversions

L will reinterpret object types as any compatible object type in assignments and when passing arguments to methods:

  • Instances of a class may be treated as instances of any super class of that class (up-casted)
  • Objects may be treated as any interface they implement
  • Instances of any interface may be treated as instances of System.Object
  • Array types may be treated as the corresponding specialization of System.Array and vice-versa
  • Proc types may be treated as the corresponding specialization of System.Proc and vice-versa

No other implicit conversions are performed. In particular there are no implicit type conversions between different integer types or between integer and character types.

Explicit conversions

Explicit type conversions are performed with a cast:

  • Widening casts between integer types sign extend.
  • Narrowing casts between integer types do not check for overflow: values not representable in the target type are silently truncated.
  • Down-casts between class types are runtime checked to ensure the casted value is an instance of the target type.
  • Casts from non-class types to class types are unchecked and are not allowed in sandbox mode

int i = cast int('A')

char c = cast char(65)

 

cast : 'cast' type '(' expression ')'

Type inference

L has a very simple type inference mechanism. You can declare local variables as type var and the compiler figures out the type from the initializer, which must be present

 

var i = 0; // i is an integer.

 

Type inference saves some typing for long winded template type names

 

var v = new Generic.Vector();

 

untyped_declaration : 'var' identifier_list ';'

Classes

The root of the L class hierarchy is System.Object. All classes inherit either directly or indirectly from System.Object.

 

L also has structs, which are similar to classes but do not support inheritance, and interfaces, which specify a protocol that any class can inherit

Names and scopes

Namespaces

An L namespace defines a named scope. Symbols within namespaces must qualified with the namespace name(s) when accessed from outside the namespace. Namespaces can be nested within other namespaces.

 

namespace N is
  class C;
  class D;
si

Outside namespace N classes C and D must be referred to as N.C and N.D

namespace M is
  class E is
    N.C c;
    N.D d;
  si
si

The individual symbols from other namespaces can be made accessible without qualification via use Namespace.Class

namespace O is
  use N.C;
  use N.D;
  class F is
    C c;
    D d;
  si
si

A use applies only to the single namespace block it appears in. System classes are in the System namespace, collection template classes are in the Generic namespace and the rest of the run time library is in Util

namespace :

'namespace' name 'is' 'si'

| 'namespace' name 'is' class_list 'si'

 

 use : 'use' name ';'

 

Scopes

Types, fields and methods occupy the same space and so it is an error to declare two symbols with the same name in the same scope even if they are of different kinds (this is different from Java where fields and methods are in different namespaces). Redefining symbols in different scopes outside of method scope is permitted but the compiler will issue a warning. Resolution of symbols by name proceeds from innermost scope outwards to global scope.

 

L generates a new scope for each block statement. It is an error to redefine a symbol previously defined in a method that is still in scope:

if i == 0 then
  // new scope here:
  var j = 1;
  while i < 10 do
    // new scope here but 
    // var j = 0; 
    // would not be legal here since j is already defined
    // in an enclosing scope
  od
else
  // new scope here, this is allowed as previous definition
  // of j is not in scope here:
  var j = -1; 
fi

Classes, methods and variables

When a variable is declared of class type it holds either a reference to an instance of that class or a class derived from it or a null reference. Objects are constructed with 'new'. Method calls are virtual. Static variables exist from program start until program exit. Instance variables exist from object construction until the owning object is garbage collected.

Variables

Variables hold a value - either directly or a reference. Variables of primitive type hold the value directly and assignments to the variable change its value. Variables of array or class type hold a reference to their value and assignment to the variable causes the reference to point to the new value. Variables that hold a reference may also take the value null (reference to no value).

Classes

Inheritance

L supports single class inheritance and a class heirarchy with a single root, System.Object. A class definition indicates what class it inherits from via isa.

class Thing // inherits from System.Object by default
    ...
si

class SpecialThing isa Thing is // explicitly inherits from Thing
    ...
si

In addition to inheriting from its super class, an L class can inherit from any number of interfaces with do.

interface Action is
    void doSomething();
si

class Actor do Action is // inherit from System.Object and also Action interface
    void doSomething() is
        ...
    si
si

Access

The default access for methods, enumerations and classes is public. The default access for fields is private.

 

class C isa System.Object is
  int a; // implicitly private
  protected int b; // accessible from subclasses
  public c; // accessible from anywhere

  void init(int a, int b, int c ) is // implicitly public
    this.a = a;
    this.b = b;
    this.c = c;
  si
si

Templates

Template are defined with formal type parameters listed in angle brackets. A concrete specialization of a template class is made whenever a new unique combination of arguments is encountered

There are no constraints on what types may be substituted for a given template's formal parameters but if the resulting specialization relies on operations not supported by an actual type argument then that specialization will not compile and an error will be issued

class D<T,U> is
  T a;
  U b;

  void init(T a, U b) is
    this.a = a;
    this.b = b;
  si
si

...

var d = new D<System.String,int[]>("Hello World", {1, 2, 3});

Class definition grammar

class_specifiers :

access_specifiers

|

 

class :

class_specifiers 'class' identifier generic implements class_body

| class_specifiers 'class' identifier generic 'isa' name generic_super implements class_body

 

implements :

'do' type_list

|

 

generic :

'<' plain_identifier_list '>'

|

 

generic_super :

'<' type_list '>'

 

class_body :

'is' 'si'

| 'is' class_body_declarations 'si'

 

declarations :

span class=SpellE>class_body_declaration

| class_body_declarations class_body_declaration

 

class_body_declaration :

field_declaration

| method_declaration

| native_declaration

| access_specifiers field_declaration

| access_specifiers method_declaration

| access_specifiers native_declaration

| enumeration

| pragma

Methods

Methods can be defined only within classes. Methods are public by default

Normal methods

 

normal_method_declaration : type identifier declare_arguments method_body

 

declare_arguments :

'(' declare_argument_list ')'

| '(' ')'

 

declare_argument_list :

argument_declaration

| declare_argument_list ',' argument_declaration

 

argument_declaration : type identifier

 

Default return value

if a method has a non-void return type then it will return the default initial value of the method's return type if execution reaches the end of the method without encountering a return statement

 

Reference parameters

Parameters can be passed by reference:

int swap(int ref a, int ref b) is
  int t = a;
  a = b;
  b = t;
si
...
int x = 10, y = 20;
swap(x, y);

Accessors

Accessors, also called properties, setters, getters or mutators, are members of a class that appear to be fields but which, when accessed, call methods to get or set the value.

 

class C is
  int value;

  void init();

  // readable int accessor C.Value:
  get int Value is
    return value;
  si

  // assignable int accessor C.Value:
  set int Value = v is
    value = v;
  si
si

...
var c = new C();

c.Value = 123; // calls set int Value accessor with v = 123
var i = c.Value; // calls get int Value accessor and assigns its return value to i

There is no requirement for get and set accessors to be paired and they can be overridden, overloaded and made public/protected/private separately. Accessors are instance methods by default declared instance or class (static).

 

accessor_declaration :

'get' type identifier method_body

| 'set' type identifier '=' identifier method_body

Indexers

L indexers are a mechanism for making instances of a class respond to array syntax, which is useful for classes implementing collections.

// a class that can be accessed as an array of integers
// indexed by letters starting with 'A':

class A is
  int[] value;

  void init(int size) is
    value = new int[size];
  si

  // a readable int indexer with index type char:
  get int[char index] is
    return value[cast int(index - 'A')];
  si

  // an assignable int indexer with index type char
  set int[char index] = v is
    value[cast int(index - 'A')] = v;
  si
si 

...

var a = new A(26);

// call A's set indexer with index = 'A', v = 123:
a['A'] = 123;

// call A's set indexer with index = 'Z', v = 456:
a['Z'] = 456;

// call A's get indexer with index = 'M':
int i = a['M'];

Indexers can be overloaded and there is no restriction on the type of the indexer or its index. Like accessors, get and set indexers are independent and need occur in pairs.

indexer_declaration :

'get' type '[' argument_declaration ']' 'is' method_body

| 'set' type '[' argument_declaration ']' '=' identifier method_body

Indexed Accessors

Indexed Accessors combine an accessor and an indexer to giv accessors that behave like arrays.

class B is
  int[] values;

  void init(int size) is
    values = new int[size];
  si 

  set int Value[int i] = v is
    values[i] = v;
  si

  get int Value[int i] is
    return values[i];
  si
si

...

var b = new B(10);

b.Value[0] = 123;

var i = b.Value[3];

indexed_accessor_declaration :

'get' type identifier '[' argument_declaration ']' method_body

| 'set' type identifier '[' argument_declaration ']' '=' identifier method_body

Literal procedures

Enumerations

Enumerations define a new type with an associated set of symbolic values. Enumeration values can be initialized to integers or left uninitialized, in which case they are assigned ascending values.

enum Suits is
  Hearts,
  Diamonds,
  Clubs,
  Spades
si 

enum BitMask is
  A = 0x1,
  B = 0x2,
  C = 0x4,
  D = 0x8
si

Each enumeration forms a named scope and so its members must always be qualified with their enumeration name

var s = Suits.Diamonds;
var bm = BitMask.A;

Enumerations are strongly typed and can hold only their defined values. Their values can though be explicitly cast to and from int. Variables of enumeration type that are not explicitly initialized are implicitly initialized to zero, which may not correspond to any member if the members have explicit values.

enumeration :

class_specifiers 'enum' identifier 'is' identifier_list 'si'

| class_specifiers 'enum' identifier 'is' 'si'

Statements

Assignment

Assignments set a variable or accessor to the result of evaluating an expression.

 
j = 10;

i = i + 1;

Method call

A call to any regular method is a valid statement. If the method returns a value it is discarded. Accessors and indexers are not methods in this context.

Std.out.flush();

Variable definition

A variable definition associates a name with a data type and a storage location, which may be in the heap, in static data or on the stack depending on where and how the variable is defined.

Variable definitions can appear within class bodies and within method bodies. Initializers are allowed only for static (class) variables and local variables - instance variables should be initialized in a constructor.

 

Instance variables are private by default.

 

Variables are always initialized - if no explicit initializer is given for a variable then it receives the default initial value for its type.

class C is
  int a;  // instance variable cannot have an initializer

  static int b = 123; // initializer OK

  void test() is
    var c = 456; // initializer OK, type inferred from initializer
  si
si

field_declaration : type identifier_list ';'

 

untyped_declaration : 'var' identifier_list ';'

 

local_declaration :

untyped_declaration

| field_declaration

Constant definition

A constant definition is written as a variable definition with an access type of const. Constants must always be initialized and can only be of primitive type.

const int MAX = 1024;

If-else

The L if-else statement conditionally executes a statement list if the supplied condition evaluates to true. The optional else clause is executed if the condition evaluates to false. The condition must evaluate to a value of type bool.

if c > d then
  IO.Std.err.println( "c > d" );
fi

if a == b then
  IO.Std.err.println( "a == b" );
elif a != b then
  IO.Std.err.println( "a != b" );
else
  throw new System.Exception( "excluded middle" );
fi

 

if_statement :

'if' expression 'then' block_statement else_statement 'fi'

| 'if' expression 'then' block_statement 'fi'

 

else_statement :

'else' block_statement

| 'elif' expression 'then' block_statement

While

The L while statement repeatedly executes a statement list until the supplied condition evaluates to false. The condition is tested before the statement is executed. The condition must evaluate to a value of type bool.

while i < 10 do
  i = i + 1;
od

while_statement : 'while' expression 'do' block_statement 'od'

Do

The L do statement repeatedly executes a statement list. The loop can be exited via the break statement.

do
  i = i + 1;

  if i > 10 then break; fi
od

do_statement : 'do' block_statement 'od'

For

The L for statement is a loop with an initializer, a condition and an increment any or all of which can be omitted. The initializer can be a local variable definition in which case the declared variable is valid only within the loop (however it scope, like all local variables is still method wide). The condition must evaluate to bool

for int  i = 0; i < 10; i = i + 1 do
  IO.Std.err.println( "i is: " + i );
od

for_statement :

'for' within_for_statement expression ';' very_simple_statement 'do' block_statement 'od'

| 'for' within_for_statement ';' very_simple_statement 'do' block_statement 'od'

| 'for' within_for_statement expression ';' 'do' block_statement 'od'

to-line

| 'for' within_for_statement ';' 'do' block_statement 'od'

Case

The case statement selects a statement list to execute by comparing the value of an expression to a list of possible values and then executing the statement list associated with the matching value. If no value matches then a default statement list is executed instead, if present.

int v;

case v
is 1, 2, 3:
  IO.Std.err.println( "v is 1, 2 or 3" );

is 3, 4, 5:
  IO.Std.err.println( "v is 4, 5 or 6" );

default:
  IO.Std.err.println( "v is something else" );

esac

statement :

'case' expression case_list 'esac'

| 'case' expression 'esac'

case_list :

case

| case_list case

| default

| case_list default

case :

'is' expression_list ':' block_statement

default :

default ':' block_statement

Foreach

The foreach statement loops over a list of values supplied by an iterator. At the start of each iteration the loop index variable is assigned the next value produced by the iterator until no more values are available.

The iterator should either inherit from the Generic.Iterator interface or the deprecated Util.Iterator class or it can simply implement the two methods in the iterator protocol

v = new Generic.Vector()
v.add( { "A", "B", "C" } );

foreach var s; v.Iterator do
  // s will take values "A", "B" and then "C" in turn:
  IO.Std.out.println( "s is: " + s );
od

a = { 1, 2, 3 };

foreach var i; a.Iterator do
  // i will take values 1, 2 and 3 in turn:
  IO.Std.out.println( "i is: " + i );     
od

foreach_statement :

'foreach' type identifier ';' expression 'do' block_statement 'od'

| 'foreach' type identifier ';' expression 'do' block_statement 'od'

Break/Continue

The break statement breaks out of loops and case statements and the continue statement returns control flow to the head of loops. Loops and case statements can be labelled and break and continue can reference these labels to break out of nested loop or case statements

outer: foreach var i; 0..9 do
  foreach var j; 0..9 do
    if i * j == k then
      break outer;
    fi
  od
od

A break or a continue statement within the body of a try statement is not supported and will result in an error message

Return

The return statement jumps to the end of the method, executing any finally statements encountered on the way. If the return type of the method is not void then the return statement must return a value. If the method return type is void then no value can be returned.

int f(x) is
  return x * 2;
si

void p() is
  return;
si

Try/catch/finally

The try/catch/finally statement handles thrown exceptions. Catch clauses receive exceptions thrown within the try block and the finally statement is always executed irrespective of whether any exceptions are caught or pass through all catch causes uncaught.

try

    ... code ...

catch SomeExceptionClass e

    ... handle SomeExceptionClass ...

catch OtherExceptionClass e

    ... handle OtherExceptionClass ...

finally

    ... always executed ...

yrt

There is a maximum of one active exception per thread. Catching an exception stops exception propagation. Throwing a new exception within a finally handler replaces the active exception with the newly thrown exception. Returning a value within an exception handler cancels the active exception

L does not insist on a particular order for catches but if a more general catch is placed before a more specific one then the second catch may never receive an exception:

try
  int[] a = new a[10];

  // throws ArrayBoundsException which is a
  // sub-class of System.Exception

  a[-1] = 1;
catch System.Exception se
  // this will also catch ArrayBoundsException
catch System.ArrayBoundsException
  // this catch will never see an exception
yrt

Each catch block forms a scope including the exception variable definition so each exception variable is valid only within its catch statement.

Throw

Exceptions are thrown with the throw statement. Only objects that inherit from System.Exception may be thrown

class BadArgumentException isa System.Exception is
  void init( System.String message ) is
    super.init( message );
  si
si

...

if i < 0 then
  throw new BadArgumentException( "i < 0" );
fi

Import

The import statement imports code from other L files or from library files into the L program.

// search the import path for a file named 'stream.l' and make all types
// defined in this file available to this program:

import stream;
// link against the PostgreSQL client library libsq:

import "sq";
// link an external object file produced by another compiler:

import "test.o";

All classes defined in an imported file (and all the files that file imports) are visible throughout the program irrespective of where an import statement appears. A given file is imported only once regardless of how many import statements request it. The search order for imports is: the current directory, any library directories specified on the compiler command line and then the standard library directories.

Importing a library or an object file is an unsafe operation and is not allowed in sandboxed mode.

Expressions

Values

A number of different sorts of value are possible in L expressions:

Literals

  • double quoted strings, which create an instance of System.String:
  • "this is an L string"

    a subset of C-style escapes are supported including:

    \n \t \x

  • single quoted characters:
  • 'x'

  • back quoted strings generate a value appropriate for passing to C code (nul-terminated with type 'char ptr'):
  • `Hello World`

  • integer, long, word and numeric char literals:
  • 
    10 // int
    123L // long
    -27l // long
    1000W // word
    0xFF10w // word
    10c // char
    
    
  • bool literals:
  • true
    false
    
  • array literals:
  • {1, 2, 3, 4, 5} // type is int[] because all elements are integers
    {"A", "B", "C" + "D", null} // type is String[]
    {1, 2, "A", "B"} // error, because no one type is assignable from all elements
    new String[] { "A" + "B", "C" + "D" } // force a specific type
    
  • Procedure references
  • A procedure reference is a value of a proc type

Unqualified variable or accessor names

x

y

Qualified variable or accessor names

IO.Std.err

Enumeration.MEMBER

Test<int>.Length

Unqualified method calls

method(a + b);

Qualified method calls

IO.Std.err.println("hello world");

Sort<int>.sort(list)

Array elements

a[i + 1]

All array accesses are bounds checked at run time and an out of bounds index results in a System.ArrayBoundsException being thrown

New object creation

new Generic.Vector<int>(30)

Pointer dereferences

[p + 3]

Pointer dereference is an unsafe operation and is not permitted in sandbox mode

Parenthesised expressions:

(x + y)

Boxed values

L does not auto box values of primitive types but values can be explicitly boxed using the .box attribute. The .boxattribute can also be applied to values of class type, in which case it simply returns the value unchanged. This can be useful in template classes as values of any type can be boxed this way to obtain an instance of some sub-class of System.Object.

Method calls on values of primitive types

Values of primitive types implement a small set of methods that give them a subset of the protocol of System.Object including toString(), toInt() and hash()

Null value

The null value null is compatible for equality comparisons with any value whose type is an object, an array or a pointer. It is compatible with any type in assignments where it gives the assigned variable its implicit initialization value.

Proc references

Attributes

L also defines some special fields that give access to attributes of values they're applied to:

Length

.length, when applied to arrays returns the number of elements in the array

int[] a = new int[10];
IO.Std.err.println( a.length ); // prints 10

Address

.address, when applied to a variable returns a pointer to its storage location (note for objects this is the address of the reference to the object, which might not be what you expect - for the object's address simply cast the reference to a pointer)

int i = 123;
int ptr p = i.address;

when applied to an array returns a pointer to its storage location of the first element of the array. This pointer is suitable for passing to C code

int[] a = new int[10];
int ptr p = a.address;

Sizeof

.sieof, when applied to a type returns the number of bytes of storage a variable of that type occupies (for class types this returns the size of the reference to an object of the class type - i.e. one machine word)

Struct

.struct, when applied to an object returns a pointer to the first field in the object. This pointer is suitable for passing to C code that expects a C struct

None

.none, when applied to a returns the implicit initialization value for that type. Unlike null, which is merely compatible with other types, the T.noneattribute is a genuine value of type T and can appear anywhere a literal or value of type T is allowed.

var  i = int.none;
var o = System.Object.none;

Box

.box, when applied to a basic type returns a value of object type representing the same value. The class of the resulting object depends on the type of the boxed value:

int  i = 123;
System.Int oi = i.box; // .box applied to an int constructs an equivalent System.Int object

Operators

Arithmetic

Signed integer operations:

Precedence

Operator precedence is:

  1. literals, this, parentheses, cast, new, field access, method call, array subscript, pointer dereference
  2. - unary minus
  3. ~ unary bitwise not
  4. *, /, % multiply, divide, modulus
  5. +, - add, subtract
  6. <<, >> shift left, shift right
  7. &, |, ^ bitwise and, bitwise or, bitwise xor
  8. ==, !=, >=, <, <=, =~, !~ equal, not equal, inequalities, object equal, object not equal
  9. !, boolean not
  10. &&, || short circuit and, short circuit or

L operators == and != compare for reference equality on objects, arrays and pointers.

System.String s = "test";
System.String t = "test";

// s and t reference two distinct (although equal in value)
// objects:
if s == t then
  IO.Std.err.println( "not reached" );
fi

L has an additional pair of operators, =~ and !~, for testing equal and not-equal that operate exactly like == and != on all non object types, arrays and on null references but behave specially with non-null object operands. These operators are intended to be overridden to provide a value equality operation (see below)

System.String s = "test";
System.String t = "test";

// s and t have equal values:
if s =~ t then
  IO.Std.err.println( "reached" );
fi

Operator overloading

L supports operator overloading of dyadic operators where one or both operands is an object. Operators are created by defining operator methods with standard names. Operator method calls are resolved by looking in both object operands for the best matching method.

 

Overloadable operators are:

  • =~, !~ : define bool operator=~(T t) to return true if this is equal to t
  • + : define T operator+(T t) to return this plus t
  • - : define T operator-(T t) to return this minus t
  • * : define T operator*(T t) to return this times t
  • / : define T operator/(T t) to return this divided by t
  • % : define T operator%(T t) to return this modulus t
  • & : define T operator&(T t) to return this and t
  • | : define T operator|(T t) to return this or t
  • ^ : define T operator^(T t) to return this or t
  • >> : define T operator>>(T t) to return this shift arithmetic right t
  • << : define T operator>>(T t) to return this shift left t
  • >, >=, <, <= : define int operator>>(T t) to return 1 if this is greater than t, 0 if this is equal to t and -1 if this is less than t
class N is
  int v;

  N operator+( N n ) is
    return new N( v + n.v );
  si

  N operator=~( N n ) is
    // note: compiler ensures opEquals only called if
    // both this and n are non-null:
    return v == n.v;
  si

  int operator>( N n ) is
    // compiler calls this for any inequality operation.
    // we must return negative, zero or positive depending
    // on ordering of this and n:
    return v n.v;
  si
si


...

N n, m, o;

n = m + o; // calls m.operator+(o)

if n > m then // calls m.operator>(o)
  ...
fi 
site design and content copyright (C) jeek 1995-2010     [ 13132 ]