Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

JS Native Compilation

Lucas Werkmeister edited this page Jun 19, 2016 · 1 revision

Interweaving native code

The ceylon-js compiler can interweave native JS code with the code generated from Ceylon sources. Simply add files with .js suffix to the specified sources and the content of those files will be copied to the resulting module.

This code will be added as is, no transformations are done on it. The text is simply copied. This means if you want to export anything, you must do it manually. The exports object is named ex$ for brevity, so you can add a line such as ex$.myfun=myfun; to export your own native JS function.

Compiling native declarations

For declarations annotated native, the compiler can look for a corresponding native JavaScript file and, if found, it will use its contents instead of compiling the declaration (or definition). In general, the native file must have the name of the declaration but with a .js suffix; however the are some additional rules depending on the type of the declaration:

Top-level methods

Top-level native methods such as these:

shared native void declared();
shared native Boolean oneliner() => false;
shared native void defined() {
  print("OK");
}

The compiler will look for files named declared.js, oneliner.js and defined.js in the same directory that contains the file with the Ceylon source, and insert their contents instead of compiling these methods.

If declared.js is missing, it will be a compilation error. But if oneliner.js or defined.js are missing, the native Ceylon code is compiled.

The native code must include the function definition only, like this:

function declared() {
  console.log("Native function");
}

The parameters to the native function should correspond to those of the Ceylon method. If the method has type parameters, they are passed to the method as the last argument:

shared native generic<A,B>();

This needs to be something like this:

function generic(targ$) {
  //targ$.A$generic is the type argument A
  //targ$.B$generic is the type argument B
}

Member methods

You can have a class with some methods implemented in Ceylon and some other methods defined native:

native class A() {
  shared void ceylon() {
    nat1();
    nat2();
  }
  native nat1();
  native nat2() => print("compiled nat2");
}

The rules are similar in respect to what happens if a native file is found or not, but the native files must reside in a directory called A under the same directory containing the file with the Ceylon source.

Native classes

Continuing from the previous example, you can have a file named A.js next to the A directory (not under it); if found, that file is used for the class definition. It will have to include the code to create a new instance, initialize it (call superclasses, satisfied interfaces, initialize type arguments, etc) and return the new instance. You will rarely need to define a native class initializer; more often, you may just want to add some extra stuff to the new instance before or after it's initialized. For this, you can define one or two JS functions that will be included (if they exist) and invoked before and after the initialization code. These functions will receive the same arguments as the class initializer, and one or two additional arguments: the instance being initialized, and the type arguments (if any). These functions don't need to return anything, and they can even be anonymous:

function(a) {
  a.someNativeAttribute=1;
}

If this function is inside a file called A_cons_before.js then it will be called right after the instance is created. If it's inside a file called A_cons_after.js then it will be called after the last statement of the class initializer, right after the instance is returned.

Native attributes

You can also have native top-level or member attributes. Top-level attributes work like this:

native Integer top1;
native Integer top2=2;
native Integer top3 => 3;
native Integer top4 {
  return 4;
}

For the first case, top1.js must exist or an error occurs. top1.js must only contain the value assigned to the toplevel attribute; is can be something as simple as 1 or something more complex such as a function that is immediately called.

The other three variants are optional; if native files are not found, the Ceylon code is compiled. For the second case, the contents of the native file must have the same format as for the first case: just the value that will be returned (without even the keyword return or a semicolon at the end).

For the third and fourth cases the contents of the native file will be inside a function call, so you can execute anything, as long as at the end you return a value. It can be something like this:

console.log("returning some value");
return -1;

For member attributes, the rules are the same, except that the files are searched for in a directory with the containing type's name (similar to member methods), and the code for all member attributes must follow the format of a getter (any number of statements, return a value at the end).

###Native Interfaces

Native interfaces work the same way as native classes, except that there's no initializer code, so they're simpler: you just have to place your native method and attribute definitions on the apropriate directory.

###Native constructors

If you need to define a native constructor, just place the function under the directory with the same name as the type's constructor. The native code must contain a function with the constructor's name, which is <Type>_<Constructor>, and must have as many parameters as the constructor, plus one or two more: If the class has type parameters, that will be the next-to-last argument; and the last argument will be the instance (which may or may not exist at this point). The function must contain the initialization code for the type, the code to create a new instance if needed, a call to the type's inheritance initializer (only available for types that have constructors), and the full initialization code for the instance:

function MyType_MyConstructor(typeArguments,inst) {
  $init$MyType();
  if (inst===undefined)inst=new MyType.$$;
  MyType$$c(typeArguments,inst); //This is the inheritance initializer
  //Any initialization code goes here
  return inst;
}