Mixed stuff

Hans-Dieter Dreier Ursula.Dreier@ruhr-uni-bochum.de
Thu, 04 Mar 1999 04:13:38 +0100



Matthew Tuck schrieb:

> Well every type has to be laid out so that it can support get/sets,
> since it is unknown whether implementations will use a variable or
> computation.

I would think that the compiler knows the implementation (at least the
signature, that is, whether it is a method call or a data member) of all
accessible items in the superclass. Then it would not need to generate get/set
calls unless necessary. Using calls to access an item that can be directly
accessed carries a *huge* performance penalty.

> >> Hmm, I think the JVM does something to avoid this - can't remember what
> >> though.
> > If you find out, please let me know.
>
> I did a search on fragile superclass.  The answer is that Java does not
> work out the layout of its classes until runtime, which in Java is link
> time.
>
> We could easily do the same thing with ASTs, identifiers are used rather
> than offsets.  Link time is then the final monolithic step where modules
> are combined and program wide optimisation might be done.

I don't like this - it seems to make incremental linking difficult if not
impossible. Actually, in my model there is no link time (other than for loading
dynamic libraries), so it wouldn't be applicable. I think it is important that
the compiler/editor is able to access objects even if their class has not yet
been fully compiled (some methoids are missing) - this would be impossible.

> See "http://java.sun.com/docs/white/langenv/Interpreted.doc1.html".
>
>
>
> > BTW, if we implement weak pointers, table elimination may be done by
> > GC. There's a lot of eliminations GC can do for us.
>
> Well eliminations are a mark-sweep algorithm, which is the same
> algorithm nonincremental GC uses.  Often the difficult part of
> elimination is determining just what the relationships are.  Things like
> dynamic dispatch complicate the situation.  How are weak pointers
> relevant?

Example: If you have an index to all the objects of some kind and you don't want
this index to interfere with GC. That is, objects may be collected even if they
are mentioned in the index, using weak references. Those would be set to NULL
when the object is being collected, in order to avoid dangling pointers. I don't
think weak references are an important feature. Implementing them afterwards
would have a heavy impact on GC, however.

> > If either class were deleted, the conversion would also be deleted.
>
> While you're at it, why not anything that uses the type or
> implementation?
> Maybe something like a view which shows the user what elements use other
> declarations that don't currently exist, and let you delete them.  It
> can be ia view since it's really a presentation of the AST.  Again, I
> think deleting it automatically might not be good if you intend to
> replace the type.

Yeah, you're right. Automatic deletion is something to do very careful, if at
all.

> > I wrote an interpreter many years ago that had an extremely simple yet
> > powerful syntax (no static type checking, though). I always got decent
> > error messages.
>
> Do you still have it around as an example?

Sure. The syntax is quite simple:Program ::= Expr | Expr Infix Expr
Expr ::= [Prefix...] [Operand] [Postfix...]

where ... means a repetition and [] that it is optional.
If the operand is missing, void will be assumed.
The parser used a syntax table similar to the one in the parser skeleton.
There were just a few postfix ops (e.g. the opening parenthesis of a routine
call).
Checking matching operators () [] do od was done using executors (see parser
skeleton).
The VM directly interpreted text, using two stacks: An operand stack and an
operator stack.
A single flat name space was used. Parameters were identified by numbers rather
than names. Dynamic (weak) typing. Used a CUI.
Here is an example (Towers of Hanoi):

C,Hanoi
 wdopen Window;
 intercept := 1;
 do display OutMask;
    while input InMask;    // arbitrary number of while's and until's allowed,
                                    // being just a shorthand
    clear Disk;
    Setup (number Height);
    P (1, 2, number Height);
    write (0,ctos 7)  // Beep
 od;
 wdclose;
*

C,P
 if arg 3 > 0
    then P (arg 1,
     6 - arg 1 - arg 2,
     arg 3 - 1);
    Output (arg 1, arg 2, arg 3);
  P (6 - arg 1 - arg 2,
     arg 2,
     arg 3 - 1)
 fi
*

C,Output
 Disk [arg 3] := "";
 display Disk;
 Disk [arg 3] := Preset [arg 3];
 row Disk [arg 3] := Pegel [arg 2];
 col Disk [arg 3] := col Preset [1] + K * (arg 2 - 1);
 display Disk;
 Pegel [arg 2] - := 1;
 Pegel [arg 1] + := 1;
// 500 repeat od;
 if kbstat then stop fi
*

C,Setup
 list row Disk [1] := 5;
 list col Disk [1] := 0;
 list Disk [1] := "";
 (arg 1) repeat                                        // count down from arg 1
to 0
   row Disk [count] := count - arg 1 + 15;  // count is the keyword for the loop
counter
   col Disk [count] := col Preset [count];
   Disk [count] := Preset [count]
 od;
 Pegel [1] := 15 - arg 1;
 Pegel [2] := Pegel [3] := 15;
 display Disk;
 K := col Preset [1] + lng Preset [1];
*

W,Window
08,24,01,79,1,1,1,1,1,0,850,1,%00 ,

O,OutMask
02,32,Towers of Hanoi
04,35,Height:

I,InMask
04,42,01,02,00,%00$FA,%00$FA,

Height
F!
 N!+/.
  infield != "" & number infield <= 10    // sort of a message handler
*

O,Preset
00,05,         л
00,05,        ллл
00,05,       ллллл
00,05,      ллллллл
00,05,     ллллллллл
00,05,    ллллллллллл
00,05,   ллллллллллллл
00,05,  ллллллллллллллл
00,05, ллллллллллллллллл
00,05,ллллллллллллллллллл

O,Disk
00,05,
00,05,
00,05,
00,05,
00,05,
00,05,
00,05,
00,05,
00,05,
00,05,

V,Pegel            // An array with 4 elements, having a capacity of 0, 10, 10,
and 10 bytes
0,
10,
10,
10,

V,K
10,

Obviously, the environment is quite outdated now. But IMO the language itself
had some features not found very often: Functions could return lists as well as
single values; many operations could perform on lists as well as on single
values, for example...

list row Disk [1] := 5;

would set the rows of all the output fields in Disk to 5.
Every construction (including loops) would yield a value. The semicolon was a
normal infix operator ("voiding" its left operand) as well as the comma ("list
appending").

Because of this ability to reuse the result of each expression (combined with
some other tricks), only few variables were needed for intermediate results,
which IMO adds to program clarity.

In those old days everything had to be extremely compact. The whole thing
(interpreter, runtime and interpreted program) had to fit into 128 KB memory.
This is why no precompilation was done (parsing overhead was 5%, so the benefit
would have been limited anyway).

> > BTW, if you lean back and think it over, you may find that the compiler
> > already *does* a lot without asking you explicitly. I found that out the
> > hard way when I wondered why something wasn't doing what I wanted it to
> > do: Wrong (local) assumed storage class of some intermediate value (that I
> > wasn't even aware of), which went out of scope prematurely. Sure this is a
> > beginner's mistake, but doesn't it fall into the same category?
>
> I'm not quite sure of your example.  Could you elaborate?

It was an initialisation, similar to ...SomeClass xyz (CString ("abc"));

The CString was created on the stack (which I didn't know by then), passed to
the constructor of SomeClass, assigned to some SomeClass member and then
discarded, leaving a dangling pointer behind. A silly mistake, to be sure, but
if you're not familiar with the conventions...


Regards

Hans-Dieter Dreier