Over years, I was trying to understand how OO feature should be integrated into a procedural language with lexical scoping. In the recent updates of Pnuts, its class infrastructure was re-designed, based on more sophisticated internal architecture than several attempts I made in previous versions. In this article, I will explain how OO functionality has been integrated into Pnuts.
Classes in Pnuts
For example, a simple counter can be defined in Pnuts as follows.
class Counter {
i = 0
inc(){ i++ }
}
The class is compiled into an ordinary Java class. There is no difference from Java classes once it is compiled and loaded.
Attributes may or may not be typed. In the example above, the variable i represents an un-typted attribute.
Getter and setter methods, which are getI() and setI() in this example, are implicitly defined for each attribute.
It is assumed that class definition follows Java Beans naming convention. Getter and setter methods are called when reading (or writing) the attribute from scripts.
c = Counter()
c.i ---> 0
c.i = 1
c.i ---> 1
Return type and parameter types of methods are optional. If no type is specifed, Object.class is implicitly used, except when the method overrides an existing method defined in a superclass; in this case it inherits the method signature.
No access control can be specified for methods and constructors. Methods and constructors are always public.
The keywords this and super can be used in Pnuts as in Java. For examples, the following code illustrates how to customize a setter method.
class MyCounter extends Counter {
setI(i){
if (this.i != i) println("changed")
super.setI(i)
}
inc(){ setI(this.i+1) }
}
The following code is another example, which is a subclass of HashMap that automatically creates a Set for unmapped key.
class MyMap extends java.util.HashMap {
get(key){
if ((v = super.get(key)) == null){
super.put(key, v = new java.util.HashSet())
}
v
}
}
Class Loading
Classes defined in Pnuts can be loaded in either of two different ways. First, script files with suffix .pnc can be automatically compiled and then loaded by PnutsClassLoader, as if it were .class files which can be loaded by system class loader. Since dependency between classes is automatically resolved by the class loader, it is less likely that linkage errors (or duplicated definition errors) occur, than the other options.
Alternatively, class definition can be embedded in a script file and executed as an ordinary script file. In this case, the classes are temporarily defined in the script, that is, the scope of class name is inside the script file that contains the class definition. When script 'A' executes script 'B', those scripts uses different class loaders to load embedded classes.
Implementation
When Counter class is compiled, the compiler generates a skelton class of the specified signature. At the same time, the compiler creates a function that creates a closure. The function returns an array of functions, each one is associated with a method of the class.
Attributes are mapped to local variables of the function. Getter and setter methods (and the associated functions) to access the attribute are automatically generated.
For example, the following function is created for the Counter class that was previously defined.
function (this, super){
i = 0
[
function inc(){ i++ },
function getI(){ i },
function setI(_i){ i = _i }
]
}
Next, the class below illustrates what the skelton class looks like. It has a method and a set of fields to bind Pnuts functions. The constructor calls the function above and initializes private fields with each element of the array returned by the function. It does not contain actual code for the method body. Instead, each method just calls a Pnuts function bound to the associated private field.
Since Pnuts function has lexical scoping rule, classes defined in Pnuts also have the same scoping rulle. If a class is defined in a local scope, symbols in the local scope are visible from the class definition. For example, the following function create an instance of counter, which the variable used in the inc() method comes from the parameter of the function.
function counter (i){
new Object() {
inc(){ i++ }
}
}
Similarly, a class attribute can be referenced from functions defined in a method.
class Counnter {
i = 0
inc_func(){
{-> i++}
}
}
Summary
The new OO programming support is a natural extension to Pnuts.
The language is still small and simple, which is one of the goal of the language design.
Pnuts generates real Java classes on the fly. Classes defined in Pnuts can be replaced by Java class if necessary.
Classes in Pnuts has lexical scoping rule. The lexical scoping rule is preserved even when functions and classes are intermixed.