Skip to content

Make XP its own (compiled) language #52

@thekid

Description

@thekid

Scope of Change

XP will become its own language, that is, PHP with syntax additions, some of which have been described in xp-framework/rfc #8. (JIT-)Compilers will be created to create PHP 5.2 and PHP 5.3 (with namespaces) sourcecode from the new language.

Rationale

Designing the XP programming language we had the following goals in mind:

  • Get rid of the oddities in PHP - alternative syntaxes like if / endif for example or "__"-magic.
  • Overcome limitations in the PHP grammar that do not allow chaining at all points like method calls after new() and array access after method calls.
  • Support syntactically what the XP framework has built ontop of PHP: annotations, type-safe enums, return types, thrown exceptions, finallly, with() blocks.
  • Integrate with the XP Framework's foundation classes.
  • Keep the "change and run" spirit and the ability for rapid development.

Generally speaking, we've tried to follow the "less is more" principle and tried making the syntax more concise as to what it's doing.

Functionality

Like in the XP framework, the entry point is always a class. In their most simple form, these classes have a static main() method. An example:

public class HelloWorld {
  public static void main(string[] $args) {
    util.cmd.Console::writeLine('Hello World from ', self::class.getName(), '!');
  }
}

Now you will already start noticing things:

  • Classes may also have modifiers.
  • The "extends Object" is optional and added by the compiler if omitted.
  • The keyword "function" is gone and replaced by the return type. Because
    the main() method does not return anything, we use "void".
  • An array type is written as component[]
  • Variables still have dollar signs. This makes it easy to spot them,
    that's why we've decided to keep this!
  • Fully qualified classnames are written with dots.
  • The object operator is also a dot (at the same time, the string
    concatenation operator is now the tilde, ~).

Compilation

To run the above example, it first needs to be compiled:

  # Compile source to HelloWorld.class.php
  $ xcc HelloWorld.xp
  ...

  # Now run it, as usual
  $ xp HelloWorld

The compilation process may produce warnings and errors. The latter lead to a failure, while warnings are only informational. Examples of errors are parse errors as well as syntactical and structural errors where the
compiled code itself would be erroneous - like method bodies in interface declarations or unresolveable types. Warnings depend on the error handler installed in the compiler - in a JIT-compiler, where things have to go
fast, no warnings may be issued at all as their computation takes time. In the standard error handler, type mismatches or missing members are warnings, for example. In a more pedantic version, missing api docs or
usage of deprecated features will be reported.

Typing

The XP language knows about the following types:

  • Primitives
    The following primitives exist: int, float, string, bool
  • Reference types
    Classes, interfaces and enums, e.g. lang.Object, util.log.Traceable
    and util.DateInterval
  • Array types
    An array is a zero-based and continuously numbered list of any type,
    e.g. string[] or util.Date[].
  • Map types
    A map is a hashtable mapping string keys to any type, e.g. [:string]
    or [:lang.XPClass]
  • The variable type
    Marks a type that may either be a primitive or any reference type,
    declared with the keyword "var". The compiler will not be able to
    verify type correctness in this case and will warn about this -
    checks will be deferred until runtime.

The following elements need to be typed:

  • A: Member variables
  • B: Method parameters
  • C: Return type

Local variables don't need to be typed or declared. Their type will be inferred on initialization (D).

Example:

public class Person {
  public string $name;                  // A

  public void setName(string $name) {   // B
    $this.name= $name;
  }

  public string getName() {             // C
    return $this.name;
  }

  public string toString() {
    $s= $this.getClassName();           // D, typeof($s) = string
    $s~= '<' ~ $this.name ~ '>';
    return $s;
  }
}

Namespaces

Namespaces are called "packages" in the XP language. A package does not exist as its own entity, instead, classes belong to a one by declaration:

package de.thekid.dialog;

public class Album {
  // ...
}

Imports

Importing is a compile-time feature to enable to use short versions of names but actually mean the longer ones. The "Hello World" example from above could be rewritten as follows:

import util.cmd.Console;

public class HelloWorld {
  public static void main(string[] $args) {
    Console::writeLine(...);
  }
}

Also available are static imports which makes writing a line to the console even shorter to write:

import static util.cmd.Console.writeLine;

public class HelloWorld {
  public static void main(string[] $args) {
    writeLine(...);
  }
}

At the same time, to avoid name clashes with PHP's native functions, these also need to be imported:

import native standard.substr;
import native mysql.mysql_connect;

To enable rapid prototyping, type import on demand can be used:

import util.*;
import static util.cmd.Console.*;
import native sybase_ct.*;

Chaining

It is now syntactically possible to continue writing after new
and to use array offsets on method return values:

new Date().toString();
XPClass::forName($name).getMethods()[0];

Varargs syntax

To create functions that accept a variable amount of arguments - printf is probably the most famous one of them, you have to resort to func_get_args() in PHP userland. The XP language supports this feature by adding ... to the parameter's type:

public class Format {

  public static string printf(string $format, var... $values) {
    // Implementation here
  }

  public static void main(string[] $args) {
    self::printf('%d args passed to %s', $args.length, self::class.getName());
  }
}

This will make the format variable contain the format string and values consist of an array with two values (the length and the class name).

Changed foreach

The foreach loop has changed from the form you know it in PHP to one inspired by C#.

foreach ($method in $class.getMethods()) {
  Console::writeLine('- ', $method);
}

Ternary shortcut

The ternary shortcut supported in PHP 5.3 upwards will be supported by the XP language:

$a= $a ?: $b;   // Same as: $a= $a ? $a : $b;

Array syntax

The array keyword from the PHP language has been replaced by the shorter form with square brackets. By means of an extension array length can be determined by using the length pseudo-member.

$a= [1, 2, 3];           // same as $a= array(1, 2, 3);
$i= $a.length;           // same as $i= sizeof($a);

Arrays can also have types:

// Instantiation
$a= new string[] { 'Hello', 'World' };

// Type
public static void main(string[] $args) { ... }

Map syntax

The array keyword in PHP can also declare maps. In XP language, this has been changed:

$a= [one: 1, two: 2];    // same as $a= array('one' => 1, 'two' => 2);

Maps can also have types:

// Instantiation
$a= new [:string] { greeting: 'Hello', whom: 'World' };

// Type
public static [:string] map(string[] $args, Closure $block) { ... }

Class literal

Every class has a static member called $class which will retrieve the lang.XPClass object associated with it.

// same as $c= XPClass::forName(xp::nameOf(__CLASS__));
$c= self::class;

// same as $c= XPClass::forName('lang.types.String');
$c= lang.types.String::class;

Finally: Finally

Especially for cleaning up - and yes, even in 2009 with the amount of memory and computing power we have available - it is still necessary to ensure, for example, file handles are properly closed:

$f= new File($name);
try {
  $f.open(FileMode::READ);
  return $f.read(0xFF);
} finally {
  $f.close();
}

Enumerations

The XP framework already offers type-safe enumerations. These were originally introduced in xp-framework/rfc #132 and are now supported with an easier-to-type syntax:

public enum Weekday {
  Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

Enumerations may also have methods:

public enum Coin {
  penny(1), nickel(2), dime(10), quarter(25);

  public string color() {
    switch ($this) {
      case self::$penny: return 'copper';
      case self::$nickel: return 'nickel';
      case self::$dime: case self::$quarter: return 'silver';
    }
  }
}

Members can have methods attached, too:

public abstract enum Operation {
  plus {
    public int evaluate(int $x, int $y) { return $x + $y; }
  },
  minus {
    public int evaluate(int $x, int $y) { return $x - $y; }
  };

  public abstract int evaluate(int $x, int $y);
}

Annotations

Also supported for quite a while in the XP Framework are annotations. They use "#" one-line comments and are parsed from the class' source when accessed inside the reflection API (see also xp-framework/rfc #16). The XP language can do without this workaround, of course!

public class ArrayListTest extends unittest.TestCase {

  [@test] public void emptyList() {
    $this.assertEquals([], new ArrayList().values);
  }
}

What happens technically:
Annotations are stored in the xp registry. The reflection API will retrieve them from there instead of having to parse the class files at runtime.

Anonymous instance creation

To generate "throw-away" instances the XP framework provides the newinstance() functionality, originally described in xp-framework/rfc #80. With the downside of having to declare the class body inside a string and the added overhead of runtime evaluation, this feature is now not only more elegant to write but classes created this way will also be declared at compile time:

$emptyFiles= new io.collections.iterate.IterationFilter() {
  public bool accept(io.collections.IOElement $e) {
    return 0 == $e.size;
  }
};

Properties

Properties are special member variables that instead of directly accessing a class field may have methods attached. This way, we can create short syntax but still stay flexible if we need to change the underlying implementation.

import native standard.strlen;

public class String {
  protected string $buffer;

  public __construct(string $initial= '') {
    $this.buffer= $initial;
  }

  public string length {
    get { return strlen($this.buffer); }
    set { throw new IllegalAccessException('Cannot set string length!'); }
  }

  public static void main(string[] $args) {
    $s= new String('Hello');
    $l= $s.length;   // 5
    $s.length= 1;    // *** IllegalAccessException
  }
}

Internally, this is implemented by compiling __get() and __set() interceptors.

Indexers

The PHP language allows for userland overloading of array operations via the ArrayAccess interface and its offset* methods. This is kind of different from the usual PHP approach with __ "magic" methods - in the XP language, it's the property syntax again:

public class ArrayList<T> {
  protected T[] $elements;

  public __construct(T... $initial) {
    $this.elements= $initial;
  }

  public this[int $offset] {
    get   { return $this.elements[$offset]; }
    set   { $this.elements[$offset]= $value; }
    isset { return $offset >= 0 && $offset < $this.elements.length; }
    unset { throw new IllegalAccessException('Immutable'); }
  }

  public static void main(string[] $args) {
    $a= new ArrayList<string>('One', 'Two', 'Three');
    $one= $a[0];      // 'One'
    $a[2]= 'Drei';    // Now: One, Two, Drei
  }
}

With syntactic sugar

A keyword "with" is added.

Usage:

with ($tree->addChild(new Node('tagname')) as $node) {
  $node->setAttribute('attribute', 'value');
}

What happens technically:
The with statement, essentialy a no-op, is resolved to the following sourcecode:

<?php
  $node= $tree->addChild(new Node('tagname');
  $node->setAttribute('attribute', 'value');
?>

Throws clause

Methods will be allowed to have a declarative throws-clause. Unlike the throws-clause in Java, blocks executing a function or method with a throws clause and not handling the list of contained exception will not lead to a compile-time error.

Declaration:

public function connect() throws IOException, ConnectException {
  // ...
}

The thrown exceptions can be retrieved by means of the reflection API.

What happens technically:
The thrown exceptions will be stored in the xp registry. The reflection API can retrieve them from there.

Security considerations

n/a

Speed impact

Overall development experience will be slower because of the necessary compilation step. The generated sourcecode will show the same runtime performance, in some cases even better.

Dependencies

None, the compiler will emit valid PHP sourcecode using the XP framework. Compiled classes can even use source classes and vice versa.

Related documents

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions