Wednesday, November 16, 2011

Another Trick for Debugging GPCP Programs

Programs compiled using the CLR version of Gardens Point Component Pascal can be debugged using the Visual Studio debugger, as described in an earlier blog.

This short blog deals with a litte trick to exploit one of the "features" of Visual Studio (VS).

When a program being debugged stops at a breakpoint, the local variable window of VS lists the
variables that are in scope in the selected frame of the call chain.  Other fields of the window show the declared type of the variable, and its concrete (actual) type. There is also a field that shows the value of the variable.  This is handy for numeric scalars, for example.

However, for other types Visual Studio fills in the value field by calling the virtual ToString() method on the object. The "miranda" option, for types that do not have a ToString of their own, is to use the method inherited from Object, which returns the name of the concrete type. This is what happens by default for Component Pascal objects.

So, the question is, can we make gpcp types display useful value information in the VS value field? The answer is, of course, yes.  But there is a trick to it.  First a couple of things that don't quite work.

Suppose that we have a pointer to record type, with no declared super type.

TYPE Foo = POINTER TO RECORD ... END;

The super type will be ANYREC, which is implemented as System.Object in the CLR version of gpcp.  However, the compiler will not import the inherited System.Object methods so attempts to override the ToString method will fail.

PROCEDURE (f : Foo)ToString() : Sys.String;

will get a "this is not at override, you must use NEW" error.  And if you try

PROCEDURE (f : Foo)ToString() : Sys.String,NEW;

the method actually will be "new", and will not override the virtual Object method. The trick is to make the compiler read in the inherited methods.  The following works:

IMPORT Sys := mscorlib_System, ... ;
...
TYPE Foo = POINTER TO RECORD (Sys.Object) ... END;
...
PROCEDURE (f : Foo)ToString() : Sys.String;
BEGIN ... END ToString;

Using RTS.NativeObject instead of Sys.Object and RTS.NativeString instead of Sys.String works also.

There is another place where the implicit use of ToString() occurs in the .NET framework.  The format methods like String.Format and WriteLine take a format string and an array of Object.  Each object in the array is converted with ToString and substituted into the numbered position in the format string.

Thus to access the format methods of the .NET libraries it is necessary to insert the values into an array of system objects.  The rules are as follows:
  • For record and pointer to record types, declare a ToString method, as described.
  • For scalar objects, like integers or reals, it is necessary to resort to the manual method of calling Sys.Convert.ToString on the scalar value.  C#, which has implicit boxing, hides this away, but Component Pascal's BOX extension does not apply to scalars.
Here is an example of a ToString method for a Coord type which has x and y values and an optional name s for each point.  We assume that the name is an open array of char, RTS.CharOpen.

PROCEDURE (pt : Coord)ToString() : Sys.String;
  CONST format = "{0}{{{1},{2}}}";
  VAR tag : Sys.String;
BEGIN
  IF pt.s # NIL THEN tag := MKSTR(pt.s^) ELSE tag := "anon" END;
  RETURN Sys.String.Format(format,
    tag, Sys.Convert.ToString(pt.x), Sys.Convert.ToString(pt.y));
END ToString;

Note that in this example we are using the fact that there is a Format method that takes three single objects following the string. We could have used the (string, object[]) version instead, and would have had to use the array version if there were more than three objects to fit in the format.

The Coord objects will now display helpfully in VS, and can in turn be directly used in other format strings.