Delphi Web Script 2.1 now at RC2

Thanks to all those who reported issues with RC1!
DWScript 2.1 RC2, available there (186 kB) or from DWScript SVN.

  • fixed D2009 compatibility
  • fixed passing of “const” open arrays
  • fixed support of exceptions raised during optimization
  • fixed memory leaks in one of the demos and in some cases of script errors
  • component “uglicons” are now visible again
  • added hints for function calls with no effect (detected after optimization)
  • added some more unit tests
  • improved error messages for various cases of incorrect code

Unless anything major is uncovered, this should be it for 2.1, and a branch will be made in SVN before 2.2 begins.

DWScript RC1 now available

Delphi Web Script 2.1 RC1 is now available from google code DWS page, you can also download it directly as a zip here (184kB).

Changes since the last SVN update are:

  • Added support for FreePascal-like compile-time $INCLUDE “macros”:
    • %FILE% and %LINE% insert the current filename and line number into the source
    • %FUNCTION% inserts the current function name, or class.method name into the source
    • %DATE% and %TIME% allow inserting the compile date/time
  • Various minor optimizations for integers and maths
  • Fixes for some corner-cases, ambiguous error reporting, a few new test cases

Everything’s got to end some day…

…and DWScript will likely enter RC phase before the year ends, as almost all of what I had in mind for 2.1 will be there!

Recent SVN additions since the last update:

  • 75% of the DWScript core code is now covered by unit tests (this was a psychological milestone for 2.1!).
  • class operators have been added, currently those are “+=”, “-=”, “*=” and “/=”, they follow a syntax similar to properties, in that a class operator isn’t defined as a special method, but as syntax sugar for a regular method (see below).
  • Function call overhead has been reduced for in-script functions & methods, as well as TdwsUnit-based functions & methods.
  • TProgramInfo now has ParamAsXxx[] properties, which allow accessing parameters by index and in a more efficient (is less-safe)  fashion.
  • Sqr() became a special function, it will now return an integer and not a float when operating on an integer.
  • Various fixes and error message clarifications (thanks Alexey Kasantsev).

(more…)

iif… anonymous expression parameters?

While making the rounds of “compiler magic” functions, I bumped on iif, the ternary operator, which f.i. Prism, VB and other support. Which looks like:

function iif (boolean; expressionIfTrue; expressionIfFalse) : value;

One part of the magic goes to the type-checking, the other part which interests me here, is that in a regular function call, all parameters are evaluated once before the call is made.
For iif, either expressionIfTrue or expressionIfFalse is evaluated, but not both, this means you can have such code as:

v := iif( Assigned(myObject); myObject.VirtualGetMethod; zero );

While with a regular function (without compiler magic), if myObject isn’t assigned, you would get an exception, as myObject.VirtualGetMethod would be invoked regardless. There are obviously countless other uses when the expressions have side-effects.

It occurred to me that in DWS, that “magic” is not only already available to internal “magic functions”, but that it could also be easily offered in the language and made no longer magic at all. It could just be another call convention, in which you wouldn’t pass a value or a reference, but something akin to a “light” anonymous expression parameter.

Would it be worth it?

Such a parameter could be qualified with a uses for instance (to reuse the keyword) rather than a var or const.

function iif( bool : Boolean; uses ifTrue, ifFalse : Variant) : Variant;
begin
   if bool then
      Result := ifTrue
   else Result := ifFalse;
end;

Would declare the iif “magic” function on variants.

Nothing would limit you to invoke a uses expression only once, so f.i.

procedure PrintNFloats(n : Integer; uses needFloat : Float);
begin
   while n>0 do begin
      Print(needFloat);
      Dec(n);
   end;
end;

PrintNFloats(10, Random); // will print 10 different random numbers

And you could use the caller’s capture for side-effects, f.i. by combining a var parameter and a uses expression parameter making use of that variable.

procedure SkipEmpty(var iter: Integer; maxIter: Integer; uses needStr: String);
begin
   while (iterator<=maxIterator) and (needString='') do
      Inc(iterator);
end;
...
SkipEmpty(iter, sl.Count-1, sl[iter]);  // with a TStrings
SkipEmpty(iter, High(tbl), tbl[iter]);  // with an array

Contrary to anonymous functions, the capture is thus explicitly declarative, and also hierarchical only (it’s only valid in functions directly called from your functions). That’s a drastic limitation, so such a syntax isn’t intended for out-of-context tasks (like closures), but for local sub-tasks, which you also guarantee will be local (something that anonymous methods can’t guarantee).

And as a final sample, in the exemple above if you want to equate the ‘hello’ and ‘world’ strings to an empty string for SkipEmpty, you could use:

SkipEmpty(iter, sl.Count-1,
          iif(sl[iter] in ['hello', 'world'], '', sl[iter])
          );

You could thus chain the expression parameters to introduce some non-traditional (for Delphi code) behaviors.

All in all, this could cover a variety of scenarios for default values, conditional alternatives, iterators, with a much restricted capability compared to full-blown anonymous methods, but with hopefully less scope for confusion than anonymous methods offer. But still, it would introduce the possibility of complex side-effects.

Any opinions? Should the possibility be surfaced or be kept only as an internal magic?

Post Scriptum:

As Craig Stuntz & APZ noted in the comments, this is similar to Digital Mars D’s lazy parameters, and both suggested using the “lazy” keyword in place of “uses” to match. However, lazy evokes more delayed evaluation, but evaluated once (as in “lazy binding”, etc.), something D doesn’t seem to support AFAICT with the lazy keyword (every use of a parameter leads to an evaluationif I’m not mistaken). While “uses” was to indicate you could “use” the parmeter’s underlying expression, as many times as you wanted to. More input welcome 🙂

Goodies from the SVN side for DWScript

Here is a summary of the recent changes:

  • support for step in the for loop structure (step value must be >=1, enforced at compile time for constants, at runtime for dynamic values). Note that step is only a contextual keyword, and not a reserved word (you can still have variables named “step” f.i.).
    for i := 0 to 10 step 2 do ...
    for i := 100 downto 1 step 3 do ...
  • support Prism-like exit syntax, ie. the following statements are now equivalent:
    Result := value; exit;
    Exit( value );
    exit value;
  • support not in form for the in operator, both following expressions are equivalent:
    value not in [...alternatives...]
    not (value in [...alternatives...])
  • break/continue outside of a loop are now detected at compile-time.
  • improved infinite loop detection: compiler will now warn about more cases of while/repeat with constant conditions and no inner break/exit.

The first elements to support language extensions are also in, more details coming in a future post!

DWScript conditional compilation directives

Current SVN version of Delphi Web Script now supports the following directives (all new but include and filter):

  • $I, $INCLUDE, $F, $FILTER: include specified file (which can be on disk or come through the virtual file system), the FILTER variants will include the file after filtering it.
    {$INCLUDE 'mysource.inc'}
  • $DEFINE, $UNDEF: define and un-define a conditional.
    {$DEFINE SPECIAL_CODE}
  • $IFDEF, $IFNDEF, $ELSE, $ENDIF: allow specifying conditionally compiled blocks.
    {$IFDEF SPECIAL_CODE}
    ...special code here...
    {$ELSE}
    ...not so special code here...
    {$ENDIF}
  • $HINT, $WARNING, $ERROR, $FATAL: output a custom compiler hint, warning or error message.
    {$HINT 'That is not wise...'}
    {$WARNING 'You should NOT be doing that!'}
    {$ERROR 'Do not do that. Period.'}
    {$FATAL 'After that, I won''t even try to compile your code!'}

Conditionals are case-insensitive names, conditional blocks can be nested.
Default conditionals when compiling can be adjusted via a new property in the TdwsConfiguration.

edit: added $FATAL