-
Notifications
You must be signed in to change notification settings - Fork 16
Development Design Natives Mechanism Implementation
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.
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 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)
As part of the object instantiation a number of steps are performed
- 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)
- Constructors are executed
- 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.
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.
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
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.
For native objects is similar just that it appends the "Object" suffix.
The following diagram visualizes the relation between a wollok object instance and its native instances.
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.
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:
- 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()
- Call the method
- Convert the returned value from java to wollok if necessary.
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 { ... }