By Erick Engelke
September 13, 2025

ECMAScript (also known as JavaScript) added significant set functionality in recent years.

For our purposes, a set is like a list of strings. It could be a list of courses, or a list of people names, etc., anything you might want to compare.

With the following unit, you can declare sets with simple logic of unions, differences, etc.

procedure TForm1.Form1Show(Sender: TObject);
var
  A, B, C : TNiceSet;

begin
  A := newSet(['a','b','c']);
  B := newSet(['b','c','d']);

  multilineedit1.Lines.Add('A = ' + dumpstringArray( SetToArray( A )));
  multilineedit1.Lines.Add('B = ' + dumpstringArray( SetToArray( B )));


  C := A.union(B);
  multilineedit1.Lines.Add('A.Union(B) = '+ dumpStringArray( SetToArray(c)) );

  C := A.difference( B );
  multilineedit1.Lines.Add('A.Difference(B) = '+ dumpStringArray( SetToArray(c)) );

  C := A.symmetricDifference( B );
  multilineedit1.Lines.Add('A.SymmetricDifference(B) = '+ dumpStringArray( SetToArray(c)) );

  C := A.intersection( B );
  multilineedit1.Lines.Add('A.Intersection(B) = '+ dumpStringArray( SetToArray(c)) );

end;

which produces the following output.

A = a, b, c
B = b, c, d
A.Union(B) = a, b, c, d
A.Difference(B) = a
A.SymmetricDifference(B) = a, d
A.Intersection(B) = b, c
Note
this will not work with Internet Explorer or the EWB IDE, but it works with all modern browsers of the last year.

Here’s the full source code.

It makes reference to nicesetutils.wbs which is included in my Nice toolkit, but I’m sharing this file here too.

unit nicesetutils;

interface

uses WebCore, webdom;

type
   TStringArray = array of string;
   external TNiceSet = class( TExternalObject )
   public
      procedure clear;
      procedure add( s : string );
      procedure delete( s : string );
      function entries: variant;
      function union( secondset : TNiceSet ):TNiceSet;
      function intersection( secondset : TNiceSet ) : TNiceSet;
      function difference( secondset : TNiceSet ) : TNiceSet;
      function symmetricDifference( secondset : TNiceSet ): TNiceSet;
      function isSubsetOf( secondset : TNiceSet ) : boolean;
      function isSuperSetOf( secondset : TNiceSet ):boolean;
      function isDisjointFrom( secondset : TNiceSet ): boolean;
      function toString : string;
      property size : integer read;
      function values: variant;
   end;


// some functions
function newSet( arr : array of string ) : TNiceSet;
function SetToArray( setvar : TNiceSet ) :  TStringArray;
function dumpStringArray( arr : TStringArray ) : string;


implementation

var
   external internal_set_arr : array of string;
   external internal_set : TNiceSet;
   external function SetToArrayInternal( arr : variant ):TStringArray;

function newSet( arr : array of string ) : TNiceSet;
begin
   internal_set_arr := arr;
   result := TNiceSet(CreateObject('new Set( internal_set_arr )'));
end;


function SetToArray( setvar : TNiceSet ) :  TStringArray;
begin
   internal_set_arr := [];
   CreateObject(' SetToArrayInternal= function( arr ) { i = 0; for (const entry of arr ) internal_set_arr[i++] = entry;}');
   SetToArrayInternal( setvar.values );
   result := internal_set_arr;
end;

function dumpStringArray( arr : TStringArray ) : string;
var
  i : integer;
begin
  result := '';
  for i := 0 to length( arr ) - 1 do begin
     if i > 0 then result := result +', ';
     result := result + arr[i];
  end;
end;


end.

The only function which is really complicated is SetToArray() which is creates an internal JavaScript function because we need to implement

  for ( const entry of arr ) ....

and EWB doesn’t support that iterator-type for loop. So we dynamically create the function and call it.