xp-framework / rfc

One of the major deficiencies in the development of many projects is that there is no roadmap or strategy available other than in the developers' heads. The XP team publishes its decisions by documenting change requests in form of RFCs.
2 stars 1 forks source link

Make XP its own (compiled) language #52

Closed thekid closed 13 years ago

thekid commented 13 years ago

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:

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:

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:

The following elements need to be typed:

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

thekid commented 13 years ago

Experiment shows this could be done via auto_prepend_file.

friebe, Sat, 28 Jan 2006 18:21:05 +0100

thekid commented 13 years ago

Maybe static imports would also be a worth a try?

<?php
import static util·cmd·Console·writeLine;

writeLine('Hello');
?>

friebe, Sun, 29 Jan 2006 02:11:23 +0100

thekid commented 13 years ago

The meta-compiler should take care of keeping the current line numbers consistent across files. One will be able to associate an error with a code line. (My) experience shows, this is an important feature.

kiesel, Tue, 31 Jan 2006 15:38:38 +0100

thekid commented 13 years ago

One may think about creating multiple compilers - or, one compiler with multiple modes: eg. debugging, release. The debug compiler would add more checks on type hints or return types while the release compiler would skip those in favor of speed.

kiesel, Tue, 31 Jan 2006 15:40:24 +0100

thekid commented 13 years ago

Rewrote this RFC completely - removed PHP4 and PHP6, added all features currently implemented.

friebe, Tue, 10 Nov 2009 13:20:04 +0100