In this post we are going to look at creating universal code that can be compiled with the RemObjects Elements compiler to work on all our supported targets.

But first, let me introduce myself. My name is Jon Lennart Aasenden. I have been a Pascal enthusiast since the early 90's. I keep active on Social Media and manage most of the Object-Pascal groups on Facebook and the MeWe network. I have also started our new group, RemObjects Developer (so if you are on Facebook, join our community!).

My background is quite varied, but I have mostly worked on low-level stuff and system services. I am the founder of Smart Pascal together with Eric Grange, which resulted in a product called Smart Mobile Studio.

Tønsberg is situated in Vestfold, Norway ~ The heart of the ancient viking culture

Before I joined RemObjects I worked at Embarcadero as the SC (Sales and Software Consultant), covering Europe, the Middle East and Africa (EMEA). I am situated in the ancient and beautiful city of Tønsberg, Norway.

Joining RemObjects is super exciting! Since I have been involved in creating compilers and languages myself, having a front-seat view at RemObjects is pretty much Xmas 24/7 for me. RemObjects is a small company, but a highly productive one. And working close to the metal is what I love.

Universal Code?

When you hear universal code your thoughts might conjure up notions of bytecodes, like something produced by a Java compiler or a .Net compiler. Those can indeed also be regarded as universal, providing there is an engine available to process and execute that code.

The code that I define as universal though, is code that is written in such a way that our Oxygene Pascal compiler can use the code anywhere, regardless of target. The rules for such generic code are simple:

  • Avoid pointers
  • Stick to intrinsic data-types
  • No system calls

Just to be clear: pointers are supported by Oxygene, even if you compile for WebAssembly ("wasm"). But as with all disciplines, what you can do and what you should do are two different things. I try to avoid pointers as much as possible to maximize portability.

The immediate benefit of writing code that sticks to these rules is that you can easily move it between compilers with little or no change. The code I will present here was originally written for Delphi, with heavy use of pointers. I then ported it to Smart Pascal, which only supports blue-pointers (a technique for using offsets into a pre-allocated buffer), and now finally I ported it to Oxygene.

It took only 10 minutes to make the code run under Oxygene, because I had already gotten rid of things that could potentially be a showstopper.

Mixing it up

One of the cool things about the Elements compiler suite is that you are not limited to just one language. Perhaps you have some code written in C# that you want to use? Java maybe? Swift? Well, with Elements you can mix and match units written in different languages.

And this is the magic, because even if you use C#, you are not bound to the .NET Framework. I mean, Object Pascal as a language is not defined by the VCL (Delphi) or the LCL (Free Pascal). Elements provides its own minimal RTL, so the language is separated from the framework and target. You can read more about the Elements RTL here: https://www.elementscompiler.com/elements/rtl.aspx

Now, it can take a little while to adjust to all this, but when the potential hits you, it hits you hard. I was speechless when I realized what we could do with this. I could take advantage of all the cool features C# has to offer, then re-apply my skills as a Delphi developer - and write code that targets Java, .NET, or raw, optimized code for ARM Linux even! That is pretty exciting stuff!

BTree lookup class

Every developer has his own "toolbox". A set of units and code that he or she have polished over many years; code that you use in many different projects. Well, since I'm new to Oxygene, the best way to get into the dialect is to port over some code. And I started with a BTree (binary tree) class:

In computer science, a B-tree is a self-balancing tree data structure that maintains sorted data and allows searches, sequential access, insertions, and deletions in logarithmic time. -Wikipedia

To sum it up: BTree is a super-fast lookup-table. You can register as many items as you want, providing each item has a unique identifier. When you later want to find an item - BTree locates it incredibly fast!

I have used this in database tables, games, search engines and collections. It is a wonderfully simple way to keep track of things -with logarithmic response time. In short: it costs very little CPU power, yet delivers incredible performance in searches.

Right, let's have a look at the code!

namespace BTree;

interface

type

  BTreeNode = public class(Object)
  public
    Identifier: Int32;
    Data: Object;
    Left: BTreeNode;
    Right: BTreeNode;
  end;

  EBTreeError = public class(Exception);

  BTreeProcessCB = public block(const Node: BTreeNode; var Cancel: Boolean) of object;

  BTree = public class(Object)
  private
    FRoot: BTreeNode;
    FCurrent: BTreeNode;
  public
    property  Root: BTreeNode read FRoot;
    property  &Empty: Boolean read (FRoot = nil);

    function  &Add(const Ident: Int32; const Data: dynamic): BTreeNode;
    function  &Contains(const Ident: Int32): Boolean;
    function  &Remove(const Ident: Int32): Boolean;
    function  &Read(const Ident: Int32): dynamic;
    procedure &Write(const Ident: Int32; const NewData: dynamic);
    procedure Clear;
    function  Size: Int32;

    procedure ForEach(const Process: BTreeProcessCB);

  end;

implementation

function BTree.&Add(const Ident: Int32; const Data: dynamic): BTreeNode;
begin
  var LNode := new BTreeNode;
  LNode.Identifier := Ident;
  LNode.Data := Data;

  if FRoot = nil then
    FRoot := LNode;

  FCurrent := FRoot;

  while true do
  begin
    if (Ident < FCurrent.Identifier) then
    begin
      if (FCurrent.Left = nil) then
      begin
        FCurrent.Left := LNode;
        break;
      end else
        FCurrent := FCurrent.Left;
    end else
      if (Ident > FCurrent.Identifier) then
      begin
        if (FCurrent.Right = nil) then
        begin
          FCurrent.Right := LNode;
          break;
        end else
          FCurrent := FCurrent.Right;
      end else
        break;
  end;

  result := LNode;
end;

function BTree.&Contains(const Ident: Int32): Boolean;
begin
  if FRoot <> nil then
  begin
    FCurrent := FRoot;
    while ( (not Result) and (FCurrent <> nil) ) do
    begin
      if (Ident < FCurrent.Identifier) then
        FCurrent := FCurrent.Left
      else
        if (Ident > FCurrent.Identifier) then
          FCurrent := FCurrent.Right
        else
        begin
          Result := true;
        end
    end;
  end;
end;

function BTree.&Remove(const Ident: Int32): boolean;
var
  LFound: Boolean;
  LParent: BTreeNode;
  LReplacement,
  LReplacementParent: BTreeNode;
begin
  FCurrent := FRoot;

  while (not LFound) and (FCurrent<>nil) do
  begin
    if (Ident < FCurrent.Identifier) then
    begin
      LParent := FCurrent;
      FCurrent:= FCurrent.Left;
    end else
      if (Ident > FCurrent.Identifier) then
      begin
        LParent := FCurrent;
        FCurrent := FCurrent.Right;
      end else
        LFound := true;

    if (LFound) then
    begin
      var LChildCount := 0;
      if (FCurrent.Left <> nil) then
        inc(LChildCount);
      if (FCurrent.Right <> nil) then
        inc(LChildCount);

      if (FCurrent = FRoot) then
      begin
        case (LChildCount) of
          0:  begin
            FRoot := nil;
          end;
          1:  begin
            FRoot := if FCurrent.Right = nil then
              FCurrent.Left
            else
              FCurrent.Right;
          end;
          2:  begin
            LReplacement := FRoot.Left;
            while (LReplacement.Right <> nil) do
            begin
              LReplacementParent := LReplacement;
              LReplacement := LReplacement.Right;
            end;

            if (LReplacementParent <> nil) then
            begin
              LReplacementParent.Right := LReplacement.Left;
              LReplacement.Right := FRoot.Right;
              LReplacement.Left := FRoot.Left;
            end else
              LReplacement.Right := FRoot.Right;
          end;
        end;

        FRoot := LReplacement;
      end else
      begin
        case LChildCount of
          0:  if (FCurrent.Identifier < LParent.Identifier) then
            LParent.Left  := nil else
            LParent.Right := nil;
          1:  if (FCurrent.Identifier < LParent.Identifier) then
          begin
            if (FCurrent.Left = NIL) then
              LParent.Left := FCurrent.Right else
              LParent.Left := FCurrent.Left;
          end else
          begin
            if (FCurrent.Left = NIL) then
              LParent.Right := FCurrent.Right else
              LParent.Right := FCurrent.Left;
          end;
          2:  begin
            LReplacement := FCurrent.Left;
            LReplacementParent := FCurrent;

            while LReplacement.Right <> nil do
            begin
              LReplacementParent := LReplacement;
              LReplacement := LReplacement.Right;
            end;
            LReplacementParent.Right := LReplacement.Left;

            LReplacement.Right := FCurrent.Right;
            LReplacement.Left := FCurrent.Left;

            if (FCurrent.Identifier < LParent.Identifier) then
              LParent.Left := LReplacement
            else
              LParent.Right := LReplacement;
          end;
        end;
      end;
    end;
  end;

  result := LFound;
end;

function BTree.&Read(const Ident: Int32): dynamic;
begin
  FCurrent := FRoot;
  while FCurrent <> nil do
  begin
    if (Ident < FCurrent.Identifier) then
      FCurrent := FCurrent.Left
    else
      if (Ident > FCurrent.Identifier) then
        FCurrent := FCurrent.Right
      else
      begin
        result := FCurrent.Data;
        break;
      end
  end;
end;

procedure BTree.&Write(const Ident: Int32; const NewData: dynamic);
begin
  FCurrent := FRoot;
  while (FCurrent <> nil) do
  begin
    if (Ident < FCurrent.Identifier) then
      FCurrent := FCurrent.Left
    else
      if (Ident > FCurrent.Identifier) then
        FCurrent := FCurrent.Right
      else
      begin
        FCurrent.Data := NewData;
        break;
      end
  end;
end;

procedure BTree.Clear;
begin
  // just decouple the references and let the GC handle it
  FCurrent := nil;
  FRoot := nil;
end;

function BTree.Size: Int32;
var
  LCount: Int32;
begin
  ForEach( procedure (const Node: BTreeNode; var Cancel: Boolean)
    begin
      inc(LCount);
      Cancel  := false;
    end);
  result := LCount;
end;

procedure BTree.ForEach(const Process: BTreeProcessCB);

function ProcessNode(const Node: BTreeNode): Boolean;
begin
  if Node <> nil then
  begin

    if Node.Left <> nil then
    begin
      result := ProcessNode(Node.Left);
      if result then
        exit;
    end;

    Process(Node, result);
    if result then
      exit;

    if (Node.Right <> nil) then
    begin
      result := ProcessNode(Node.Right);
      if result then
        exit;
    end;
  end;
end;

begin
  ProcessNode(FRoot);
end;

end.

Using the BTree class

You can freely copy the code above and use it in your own project. Simply replace the namespace with whatever you like, it's free code and you can use and abuse it as you see fit.

Using the BTree code in your projects is really simple. And I'm sure you can see ample room to expand it. But to start simple:

      var lLookup := new BTree();
      lLookup.Add($1200, "Hello World");
      lLookup.Add($1300, "This is cool");

      var lData := lLookup.Read($1300);
      ShowMessage(lData.ToString());

One thing to notice about the class is that the data it keeps track of is dynamic. This is a special data-type that can resemble variant in Delphi or Free Pascal, meaning that it can take whatever data you give it. I must stress that dynamic has none of the limitations that Delphi or Free Pascal variants have, there is no type mapping going on behind the scenes - and it's not bound to the ancient COM standard that Delphi variants were modeled on. It basically means "anything".

So, in the example above we first create an instance of the class:

var lLookup := new BTree();

Then we register two data items, just two normal strings, each with its own identifier. Please note that you can store whatever you like here, a string, a record, a class instance – anything goes:

      lLookup.Add($1200, "Hello World");
      lLookup.Add($1300, "This is cool");

And finally, we use the read() method to quickly locate item number two, with the $1300 identifier. Again, we could register 10.000 items, the BTree class would locate the item incredibly fast!

      var lData := lLookup.Read($1300);
      ShowMessage(lData.ToString());

In the code above we tell BTree to return the item with the identifier $1300. The variable LData will get the data, and then we display it using the ShowMessage() procedure.

This code can be used anywhere, be it Windows, OS X, Arm Linux, Java, .Net. It is well within the rules I outlined at the beginning of this post, and even ran on the first try under WebAssembly:

Being able to target WebAssembly directly is a great feature of the Elements toolchain

Final Words

I hope you have found this post informative. I am super excited about Oxygene, and the whole Elements toolchain is incredibly impressive!

In the weeks and months to come I will port over more and more code and demonstrate just how powerful RemObjects technology is. The development tools is just one aspect of what RemObjects delivers, so writing code that talks to Remoting SDK and Data Abstract is also on the menu!

Stay tuned and let's explore Elements together!