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.
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.
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:
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!