Static classes, quote-delimited strings, final, hints/warnings

Here is a quick summary of recent changes SVN-side for DWScript:

  • static classes are now in DWS, like the Prism/C# variant, they are classes which cannot be instantiated and can only have class methods or properties, but as DWScript supports the concept of meta-classes, unlike in Prism/C#, they can be virtual and sub-classed (unless you mark them as “sealed” too, of course).
    type
       TStatic = class static
          class procedure Whatever;
       end;

    Note that a static class can only be subclassed to a static class, and can only inherit from a static class (or TObject).

  • quote-delimited strings can now be used as an alternative to apostrophe-delimited strings as in JavaScript and Prism, though they are still single-line strings, like in JS and unlike in Prism.
    var hello := "bonjour l'ami";
  • the final qualifier is now supported, it can be used to indicate that a method can no longer be overridden by subclasses.
  • the compiler will now warn about invoking constructors on instances (rather than classes) when outside of a constructor.
  • method override checks have been made stricter and compiler should now report all cases of incorrect overrides.
  • new directives {$HINTS ON|OFF} and {$WARNINGS ON|OFF} are now supported, and allow to turn on/off the hints and warnings respectively.

DWScript 2.2 preview 3

A source archive file for DWS 2.2 preview 3 (231 kB)  is now available. This is essentially just a zip file (7zip actually) for those not using the SVN.

Changes since the last post are rather limited:

  • built-in “QuotedStr” function now accepts a second quoteChar parameter.
  • improved compiler robustness (keeps parsing and reporting more errors in more cases), clarified some compile errors.
  • improved unit tests coverage (84.6%).

Further changes since the last archive (preview 2) are listed in the following posts:

 

DWScript: objects memory model

Objects memory management in Delphi Web Script is automatic, and you never have to worry about releasing objects in a script if you don’t want to. However, there are elements of manual memory management in DWS which provide greater control, and allow to enforce correctness of object-lifetime.

For instance, in DWS you can avoid the issue that is present in traditional GC-based environments, where references to long obsolete objects can still live in memory, and can still be invoked without triggering exceptions.

Automatic Memory Management

Up to and including version 2.2, the garbage collection is implemented in DWS through reference-counting (based on Delphi’s interfaces), and supplemented by a special garbage collector which is responsible for handling and cleaning up object dependency cycles.

If you have no particular memory constraints or requirements, you can thus safely create objects in DWS and not worry about their release. The special cycles GC means DWS isn’t vulnerable like Delphi’s interfaces-based reference-counting is.

Manual destruction of objects

With DWS 2.2, destructors are being brought back in a meaningful way, i.e you can manually destroy an object that is still referenced, and the object will then acquire a special “destroyed” status. Any attempt to access a destroyed object’s fields, or invoke a destroyed object’s methods will trigger an exception.

Beyond controlled release, this mechanism can be used to make sure that an object, even if incorrectly kept around, can’t be used anymore without an exception happening.

Note that unlike in Delphi, to guarantee the correctness of the “destroyed” status, a destroyed object that is still referenced will still use some memory, for as long as it is referenced. However, that object’s Delphi-side objects and memory can have been freed, as well as the object’s memory used to store the object’s fields.

When debugging, it is possible to iterate over all script objects (via the program’s execution object), including all the “destroyed” objects which are being kept because of “dangling” references.

Cleanup of “leaked” objects

Objects that have not been manually destroyed, and are still referenced by the time EndProgram is executed will be cleaned up without having their destructor invoked. This covers the cases where the script execution has been completed (end of code reached), has been aborted Delphi-side, via an execution timeout, or terminated because of an unhandled exception.

The destructors are not invoked because when EndProgram is triggered, the script is no longer supposed to be running in a any way or form: the stack has been torn-down, and cross-object references become meaningless as the clean up forcibly proceeds.

For external objects exposed through TdwsUnit, you will get an OnCleanup event, which allows you to release your Delphi-side objects, though in OnCleanup you have to be aware that the script has already been terminated, so you shouldn’t Call script functions, or rely on other script objects still existing or being valid.

DWScript: Class const, more hints, RTTI…

Recent additions to the SVN for Delphi Web Script:

  • class constants are now supported.
  • compiler will now warn about simple cases of unreachable code.
  • new hints available when coSymbolDictionary compile option is set:
    • compiler will hint about declared, but unused variables.
    • compiler will hint about unused Result pseudo-variable.
  • SymbolsLib brought over from DWSII repository and updated, it provides RTTI functionality from within the scripts.
  • various fixes and extensions to the unit tests suite

Spotlight on TdwsSymbolDictionary

A useful, yet not very prominent class of DWScript is TdwsSymbolDictionary. It provides a reference of  all symbols mentioned in your script, where they are declared, where they are used, etc. It is optionally filled up when you compile a script with the coSymbolDictionary compile option, and can be accessed from a compiler program object or interface.

It can be used for multiple purposes, centered around IDE features and auditing. Here are a few usage scenarios.

In-place symbol help and navigation

The most basic use of TdwsSymbolDictionary is probably to figure out which symbol is under the cursor or the mouse in your code editor. You can then use the symbol to provide relevant contextual help (either from your help file, or auto-generated from the script, such as a list of parameters for a function, of fields for a class, etc.) or look up the dictionary once more, and provide information about where it’s declared, implemented, used or forwarded. That positional information can also be used to offer navigation shortcuts between declaration and implementation, or to any of the symbol’s occurrences in the source.

Auto-completion

The TSymbolDictionary can be used it to figure out what is currently being typed, by identifying the symbol at the cursor, or the last recognized symbol in the code being entered. From that point on, you can generate a list of contextual variables, methods, fields, etc. that can be used to fill up an auto-completion drop down.

Code Refactoring

Once you have a symbol pinpointed, either by name or through one of its occurrences in the source, you have access to all the places it’s occurring in the code. A trivial use of such a list is for a rename refactoring: as the list of symbol positions is already sorted by source file, line and column, you merely have to walk the list backwards and replace occurrences in your sources files.

You can also team up TdwsSymbolDictionary with  other information sources to implement more complex refactorings. For instance alongside TdwsSourceContextMap (which is also provided in a compiled program), you can implement “extract method” (or its variants, “pull up” & “push down”).

Code Auditing

The symbol dictionary can be a convenient starting place for symbol-related audits and diagnostics. All audits that relate to the amount of times a symbol is used (or not) being the most trivial.

Another straightforward usage is when you have naming conventions for fields, variables, classes etc. and/or to enforce case consistency. The later can be handled in your IDE in a similar fashion as a rename refactoring, except you only replace in suReference and suImplementation symbol positions, and it is harmless enough you can do it in a background task without requiring user interaction.

You can also use it to diagnose code issues, for instance such patterns:

someObject.someProperty[stuff].otherProperty.function.method(1);
someObject.someProperty[stuff].otherProperty.function.method(2);
someObject.someProperty[stuff].otherProperty.function.method(3);

can be heuristically detected (either as “too many member symbols on the same line” or “member symbol repeated too much in nearby lines”), and the attention brought to the hot-spots so the code can be simplified and cleaned up.

DWScript news: classes, exceptions, speedups

There have been quite a few changes, fixes and enhancements to DelphiWebScript, available in the SVN version:
  • class visibility is now enforced: private and protected are equivalent to Delphi’s strict private and strict protected. Other levels are public (members accessible to the whole script) and published (default visibility, to be used for external exposure, RPC, persistence, etc.)
  • added support for class abstract and class sealed: an abstract class has to be subclassed before it can be instantiated, a sealed class can’t be subclassed.
  • virtual methods are now based on a Virtual-Method Table, previously they were implemented in a way vaguely similar to Delphi’s “dynamic” methods. The new implementation is much faster, but at the cost of a (hopefully reasonable) memory overhead.  VMTs are shared, and thus only use memory for classes that actually introduce or override a virtual method.
  • fixes and improvements to the exception handling (ExceptObject now available).
  • fixes to the circular reference garbage collector.
  • fixes and improvements to the virtual class methods support, and properties based on class methods.
  • class-less procedures and functions calls are now faster.
  • partial inlining loop unrolling optimization for small statement blocks*.
  • other misc. optimizations, improvements and fixes.

There is also a new JSON support unit, which isn’t currently used by DWS, but has been introduced for testing and investigations. The strict JSON parser is AFAICT about twice faster than the current “fastest” Delphi JSON parser, with still some room for improvements.

*: the impact of those seem to be highly CPU-dependent, f.i. on the “Mandelbrot” demo, the speedup is a few percentage points on an AMD Phenom, but about 40% on my Intel Core i5.

edit: to be more accurate, it brings the Intel processor up to the level of the AMD cpu, the code must have been hitting a weakness in the Core i5 branch predictor.

Leaps and bounds of DWScript

A DWS 2.2 preview zip (199 kB) has been posted, it is an SVN snapshot, and features recent additions to DWS:

  • Contracts are now partially supported: ensure, require and old are available and use the same syntax as Prism contracts, inheritance is supported, class invariants are not in just yet, mostly for syntax indecision reasons, see this thread.
  • the implies operator is now supported, “a implies b” is equivalent to “(not a) or b”, or in other words, it is false only if a is true and b is false.
  • Assert() is supported, and will trigger EAssertionFailed in case of, well, an assertion failure. A compiler option has been added to control the generation of assertions.
  • Exception now has a StackTrace method, which will return a textual stacktrace script-side. Runtime errors now also include a stack trace, and new Delphi-side methods provide access to even more details.
  • the in operator has been extended to allow class overloading, f.i. you can now use “if (aString in someStringList) then…”, the not in form is also supported.

A fair chunk of the error and exception code has been refactored, if you were relying on it, all the preexisting functionality is still there, though it has been shuffled around to (hopefully) more relevant locations.

DWScript 2.1 branched

DWScript 2.1 RC2 has been promoted to stable 2.1.0 status, if you already have 2.1 RC2, you don’t need to download anything new.

If you use SVN, there is a now a “stable-2.1” branch which will see only fixes, evolutions and additions will now resume under “trunk”.

What can be expected for DWS 2.2?

In addition to the previously listed goals, which are still part of the background work, there are a few specific goals for 2.2:

  • separating execution context from the expression tree. Currently a compiled script can be run multiple times, but from only one thread at a a time, if you need to run two, you need to compile two instances (thus using up twice the memory). The goal is to allow the same program to be run from multiple threads at the same time.
  • language support for contracts programming.
  • 85% testing code coverage psychological threshold.
  • more demos/samples, at least a couple showcases of IDE/Debugger support features.