Skip to content

Development Design Natives Mechanism Implementation

Fernando Dodino edited this page Apr 3, 2017 · 7 revisions

Intro

In this section we describe Wollok internal mechanism for implementing native objects. Native objects are wollok objects that have at least one method declared as native. A native method is one that is not implemented in wollok code, but in a native way.

Currently that means in java (or any other JVM bytecode compatible language).

If you want to define a new Wollok class/object please refer to this page.

For more info on how to use native objects refer to the Language Reference - Natives

Here we will discuss the internal details of the interpreter to implement natives and not how to use them.

Interpreting classes and Constructor calls

Wollok is an interpreted language, that means that it directly reads the source code and perform some actions (execution). Currently when it reads a class declaration it does nothing. Instead the class model (ast / or semantic model) object is used whenever the interpreter finds an object instantiation. In wollok this means an instance of WConstructorCall.

val a = new Dog('lassie')

Gets parsed as

WAssignment
     |- left = WVariable
     `- right
          `- WConstructorCall
                  |- class = WClass(name=Dog)
                  `- parameters
                         `-[0] WString

When wollok finds such a constructor call it will allocate an object for you. This means creating a new instance of WollokObject class.

WollokObject

WollokObject is basically one of the most important class of wollok interpreter. It is the way the interpreter represents/models all wollok objects. Since v1.3.0 this applies to all objects: either user defined objects, or user instantiated objects, but also to "out-of-the-box" objects like booleans, numbers, string, lists, etc.

Basically all objects in wollok end up having an instance of WollokObject within the interpreter.

So, among other things this class has the method lookup implementation (which is currently not implemented in wollok itself. It could be though in the future, but that will increase the difficulty of a type system)

Object Instantiation

As part of the object instantiation a number of steps are performed

  1. All instance variables declaration are evaluated and put in the object's internal space for instance variables. (This is kind of a map. Actually ends up being like a scope/EvaluationContext)
  2. Constructors are executed
  3. Native objects are instantiated for each native class.

This all happens in the hierarchy order: Top-down

So each class: adds variables, execute constructor, instantiate native. Then same for the next subclass.

Native Object Instantiation

If a class is native (has at least 1 native method) then Wollok will instantiate a java/xtend class that will hold that wollok-class native implementation.

Native class resolution

The java/class is searched by convention based on the wollok class. It looks for a java class with the same FQN (package + class name).

For example for this:

wollok.wlk

package utils {
    
    class IOUtils {
        // some native method here
    }
}

It will try to find a java class

wollok.utils.IOUtils

Naming Exceptions

Sometimes the java class name cannot be the same as the wollok class. For example for the "Object" class. You cannot have a java class named Object (well you can, but try it and suffer yourself :P)

That's why native object instantiation is modeled as a strategy class org.uqbar.project.wollok.interpreter.natives.NativeObjectFactory

DefaultNativeObjectFactory is the current implementation which has a mapping between wollok classes and java classes.

Eventually we will have a way to customise this without rebuilding wollok.

Objects resolution

For native objects is similar just that it appends the "Object" suffix.

Instances and Native Instances relation

The following diagram visualizes the relation between a wollok object instance and its native instances.

wollok-dev-impl-natives fw

As you can see here a wollok object has the following amount of java instances

   #java_instances = 1 (WollokObject) +  1 * native_classes

And there's a one-to-one relation between a native class and a native instance for a wollok object.

Dispatch

To understand the dispatch you can start from WollokObject.call() method, that performs the method lookup. Eventually it ends up AbstractWollokDeclarativeNativeObject.call() which is the piece of code that calls the native method reflectively.

It performs the following steps:

  1. Convert parameters: your native method can declare a String parameter for example, and wollok will automatically convert from a WollokObject of type String to a java String. We do this for a couple of basic types. See WollokJavaConversions.wollokToJava()
  2. Call the method
  3. Convert the returned value from java to wollok if necessary.

Cache / Singleton

Currently wollok always instantiate a native object for each wollok object. Although for those native classes that don't need access to the wollok instance we could, in the future, have some kind of annotation based convention to instantiate it only once.

For example

@Singleton
class MyNative { ... }
Clone this wiki locally