Wednesday, April 23, 2008

A compiler

About ten years ago I started working on a compiler for the Eiffel programming language and even quit my job to work on it full time for a few months. Before I finished it, I was offered a contracting job where I would split my time between working in England and Germany. One thing led to another and now I live in Germany and look after two small daughters.

Anyway a few weeks ago I was asked to look into the C# language and that re-awakened my interest in languages and compilers, so I thought I'd have another look at it.

Since then, I've modified the old C++ implementation of the compiler to generate C code with more features of Eiffel that I'd previously left out and I am now working on an Eiffel implementation of the compiler with the lessons learned.

I was always unhappy with some of the gotchas in Eiffel, and that the compilers didn't manage to implement all the features of the language as described in the books (sometimes due to language deficiencies). I'm attempting to find ways around these deficiencies for a new version of the language.

Selecting interfaces rather than features

My view of an Eiffel class is that it provides many interfaces, one for each type it inherits from. An object provides a set of features that may be accessed using different names from different interfaces.

class D
inherit
B
rename

fn as b_fn
end
C
rename
fn as c_fn
select as A -- c_fn will be called through a C reference
end
feature
end


This needs a lot more explaining, but it will have to wait for another post, or an update. It does, however, solve the Iterating Several Actions problem (I know, I know, it's not really a problem any more, but it's been bugging me since 1995).

Callbacks

From what I've seen (although I admit, I haven't looked for some years) Agents and Tuples are not very aesthetically pleasing. In order to be able to provide effectively functions to a feature, I came up with callbacks.

The syntax of a callback definition is simple:

 a_callback ":" callback [ "(" { formal_parameters "," } ")" ] [ ":" type ]


The names of the formal parameters may be used in the callback itself, which is a compound instruction contained in braces{}.

e.g.
some_container.for_each( { other_container.extend( item ) } )

The callback can use attributes (including Current) of the class containing the callback code, the feature's parameters and locals, as well as the formal parameters of the callback. The identifier Result, used in a callback will refer only to the result of the callback, not the result of the feature.

A callback that uses feature parameters or locals may not be assigned to anything, but merely passed from feature to feature or called. (This is to ensure the locals and parameters will be available
when the callback is made.)

A callback (parameter) may be compared against Void, checked for storability (the_callback.is_storable, usually used in preconditions) or called with the same syntax as a feature call.

Preconditions in the right place

In some (all?) implementations of Eiffel the precondition checked for a routine is the precondition of the routine in the object's class rather than the interface's class. This seems to me to undermine the whole concept of these assertions by allowing code to be written that clearly breaks the preconditions of routines (since the preconditions may be weakened in subclasses).

My compiler will check the interfaces' preconditions before making the call to the object; if you know some subclasses have weakened preconditions, access them through references to those classes!

Inspect class

Back in December 2003, MacReiter made a suggestion to the Eiffel NICE Language list that the ?= operator should be replaced by a switch-like construct. I like this idea, so I'm intending to implement this variation on the theme:

 inspect class some_reference
when SUBCLASS_A then
-- some_reference is usable in this compound
-- instruction as a SUBCLASS_A reference
some_reference.subclass_a_routine
when NONE then
-- some_reference can only be Void
when GENERAL then
-- some_reference is a non-Void reference
end


The types will be checked in order from most specific to least, i.e. if a type is a subclass of another class the subclass will match rather than the superclass. If two types are listed where neither is an ancestor of the other, they will be matched in the order they appear in the inspect instruction.

Multiple formal generic constraints

Formal generics in Eiffel are constrained by a single type, e.g.:

class BINARY_TREE[T -> COMPARABLE] ...

I suspect that sometimes it may be useful to have two or more constraints on a formal generic, and that could be written:

class BINARY_NUMBER_TREE[T -> NUMERIC & COMPARABLE] ...

Then, all actual generics used to obtain a type of BINARY_NUMBER_TREE would have to be a subclass of NUMERIC and COMPARABLE, but the two classes need not be related for all time.

This would allow for more flexible class hierarchies since you can defer some decisions (like "Are all NUMERIC types also COMPARABLE?", how about complex numbers, where do they come in?)

No comments: