First steps

It is very easy to work with Smart.

Open the IDE, create a new project, select for instance a "Console" template, then code your object pascal units, just as usual.

The only missing feature is a debugger integrated into the IDE. You'll have to debug your code from the JavaScript side (using the debugging tools featured in Chrome, for instance).

You have at hand the full power and readability of the great object pascal Open Source implementation of Delphi Web Script. See http://code.google.com/p/dwscript/

No more type-less JavaScript coding, no more pseudo classes, no more endless nested delegates... Readable code, modern object oriented language (including strong typing, interfaces and inheritance), with a lot of existing and proven code base from a happy-sharing community. Just plain Object Pascal code, with the full power of the HTML 5 platform at hand.

It does not need external JavaScript library (like jQuery or such), but you can use any such library, if needed.

Conversion rules

The Delphi Web Script implementation is very close to the Delphi / FPC implementation. But it will run with JavaScript as its runtime engine: you can see JavaScript as the replacement of assembler code for a regular Delphi/FPC compiler - and you can in fact write directly JavaScript code in the middle of a Smart unit using the asm .. end keywords.

As a consequence, when implementing low-level algorithms like crc32 or SHA-256 with Smart, some genuineness is to be taken in account:

  • There is no low level binary types like byte, integer, cardinal, Int64, UInt64 which are all mapped as one integer type (since hashing use binary representation of the data, we must take care of this); 
  • You can't play with memory buffers directly in the language (this is a managed code, without any pointer nor memory allocation) - so we'll use string to handle memory buffers; 
  • You have to handle all code endianess by hand (by definition, JavaScript is endian-agnostic); 
  • There is no char type, just string.

But most high-level code could be shared between a Delphi application and a Smart application. For instance, when used within our mORMot framework, you may share the ORM definitions (via class) or the SOA contract definitions (via interface), and most of your business logic.

CRC32 computation

So let's start with crc32 algorithm.

Here is the main computation code:

var
  crc32Tab: array[0..255] of integer;

function crc32(aCRC32: integer; const data: string): integer; var i: integer; begin result := (not aCRC32) shr 0; for i := 1 to length(data) do result := crc32Tab[(result xor ord(data[i])) and $ff] xor (result shr 8); result := (not result) shr 0; end;

This is a standard implementation pattern, except for two remarks:

  • We added ... shr 0 in order to ensure that an integer variable will be maintained as an UInt32/cardinal variable. Since crc32 is a 32 bit hashing algorithm, we need to ensure that we'll only have positive values; 
  • No byte type is available here: so we'll explicitly call ... and $ff in order to truncate the integer value into its 8 bit content; 
  • Since the string type is used for data manipulation, we use ord(data[i]) to retrieve each byte (or char) of the supplied text.

This implementation of the crc32 algorithm expect a pre-computed table to be available. All JavaScript implementation of this algorithm (at least, all that I was able to found on Internet) use a fixed constant array. Since we are not afraid to write code any more in our AJAX application, and since it may help saving bandwidth and application size, we'll compute our own crc32Tab[] array content with the following code:

procedure InitCrc32Tab;
var i,n,crc: integer;
begin // this code generates a 1KB table
  for i := 0 to 255 do begin
    crc := i;
    for n := 1 to 8 do
      if (crc and 1)<>0 then
        // $edb88320 from polynomial p=(0,1,2,4,5,7,8,10,11,12,16,22,23,26)
        crc := ((crc shr 1) xor $edb88320) shr 0 else
        crc := crc shr 1;
    CRC32Tab[i] := crc;
  end;
end;

Then we are able to use this code as such, for instance in a Smart console application:

procedure TApplication.PopulateConsole;
var i: integer;
begin
  InitCrc32Tab;
console.Writeln(IntToHex(crc32(0,'TestCRC32'),8));
end;

The InitCrc32Tab shall be called only once, at application startup. Its execution is immediate. It won't delay your application display.

SHA-256

The well-known SHA-256 algorithm is a proven way of creating an unique identifier from any data input. You can use it for instance to sign any content, or store efficiently a password. It is mathematically proven to be impossible to find out the input data from its hashed reduction (at least for the next decade of computer power). And it has a very low potential of "collision" (i.e. two diverse data having the same resulting hash). It is "Top Secret" enabled - U.S. National Institute of Standards and Technology says, "Federal agencies must use the SHA-2 family of hash functions for applications that require collision resistance after 2010". This is the hashing pattern used within mORMot.

But it is also more complex than the crc32 algorithm. See http://en.wikipedia.org/wiki/SHA-2

You have an optimized implementation in the SynCrypto unit, with tuned x86 assembler code, and provided regression tests. We'll implement a pure Object Pascal version, compatible with the Smart / Delphi Web Script (DWS) compiler.

First of all, we'll define a record type. We may have used a class, but since we have an extended record type at hand with DWS (including properties and methods), we will stay to it.

type
  TSHA256Buffer = array[0..63] of integer;
  TSHAHash  = record
    A,B,C,D,E,F,G,H: integer;
  end;
  TSHA256 = record
  private
    // Working hash
    Hash: TSHAHash;
    // 64bit msg length
    MLen: integer;
    // Block buffer
    Buffer: TSHA256Buffer;
    // Index in buffer
    Index: integer;
    // used by Update and Finalize
    procedure Compress;
public
    /// initialize SHA256 context for hashing
    procedure Init;
    /// update the SHA256 context with some data
    procedure Update(const Data: string);
    /// finalize and compute the resulting SHA256 hash Digest of all data
    // affected to Update() method
    // - returns the data as Hexadecimal
    function Finalize: string;
    /// compute SHA256 hexa digest of a given text
    class function Compute(Data: string): string;
  end;

The main class function can be used as such:

  console.WriteLn(TSHA256.Compute('abc'));

And it will be implemented as:

class function TSHA256.Compute(Data: string): string;
var SHA: TSHA256;
begin
  SHA.Init;
  SHA.Update(Data);
  result := SHA.Finalize;
end;

The initialization will be done with this method:

procedure TSHA256.Init;
begin
  Hash.A := $6a09e667;
  Hash.B := $bb67ae85;
  Hash.C := $3c6ef372;
  Hash.D := $a54ff53a;
  Hash.E := $510e527f;
  Hash.F := $9b05688c;
  Hash.G := $1f83d9ab;
  Hash.H := $5be0cd19;
end;

Note that the DWS compiler will initialize all record content to zero by default. The corresponding JavaScript code will be emitted. So only Hash.? values are to be set explicitly: Index and MLen properties are already set to 0.

Then the following method will update the current hash with some supplied data:

procedure TSHA256.Update(const Data: string);
var Len, aLen, i: integer;
    DataNdx: integer = 1;
begin
  Len := length(Data);
  inc(MLen,Len shl 3);
  while Len>0 do begin
    aLen := 64-Index;
    if aLen<=Len then begin
      for i := 0 to aLen-1 do
        Buffer[Index+i] := ord(Data[DataNdx+i]) and $ff;
      dec(Len,aLen);
      inc(DataNdx,aLen);
      Compress;
      Index:= 0;
    end else begin
      for i := 0 to Len-1 do
        Buffer[Index+i] := ord(Data[DataNdx+i]) and $ff;
      inc(Index,Len);
      break;
    end;
  end;
end;

The internal Buffer[], which is expected to contain up to 64 bytes, is filled with the provided data, then the global MLen is refreshed, and the Compress method will do the proper hashing, when the 64 bytes buffer is full. The Index variable is used to track the number of bytes available in the internal Buffer[] array.

The Finalize method will compute the latest block, including the global length to the incoming data (with padding if needed), then will return the data as an hexadecimal string:

function TSHA256.Finalize: string;
var i: integer;
begin
  // Message padding
  // 1. append bit '1' after Buffer
  Buffer[Index]:= $80;
  for i := Index+1 to 63 do
    Buffer[i] := 0;
  // 2. Compress if more than 448 bits, (no room for 64 bit length)
  if Index>=56 then begin
    Compress;
    for i := 0 to 59 do
      Buffer[i] := 0;
  end;
  // Write 64 bit Buffer length into the last bits of the last block
  // (in big endian format) and do a final compress
  Buffer[60] := (MLen and $ff000000)shr 24;
  Buffer[61] := (MLen and $ff0000)shr 16;
  Buffer[62] := (MLen and $ff00)shr 8;
  Buffer[63] := MLen and $ff;
  Compress;
  // Hash -> Digest to big endian format
  result := LowerCase(IntToHex(Hash.A,8)+IntToHex(Hash.B,8)+IntToHex(Hash.C,8)+
    IntToHex(Hash.D,8)+IntToHex(Hash.E,8)+IntToHex(Hash.F,8)+IntToHex(Hash.G,8));
  // Clear Data
  Init;
end;

Some endianess tricks are used in the above code. But it remains easy to follow and maintain.

The main hash computation is performed in the Compress method, as such:

const
  K: TSHA256Buffer = ([
   $428a2f98, $71374491, $b5c0fbcf, $e9b5dba5, $3956c25b, $59f111f1,
   $923f82a4, $ab1c5ed5, $d807aa98, $12835b01, $243185be, $550c7dc3,
   $72be5d74, $80deb1fe, $9bdc06a7, $c19bf174, $e49b69c1, $efbe4786,
   $0fc19dc6, $240ca1cc, $2de92c6f, $4a7484aa, $5cb0a9dc, $76f988da,
   $983e5152, $a831c66d, $b00327c8, $bf597fc7, $c6e00bf3, $d5a79147,
   $06ca6351, $14292967, $27b70a85, $2e1b2138, $4d2c6dfc, $53380d13,
   $650a7354, $766a0abb, $81c2c92e, $92722c85, $a2bfe8a1, $a81a664b,
   $c24b8b70, $c76c51a3, $d192e819, $d6990624, $f40e3585, $106aa070,
   $19a4c116, $1e376c08, $2748774c, $34b0bcb5, $391c0cb3, $4ed8aa4a,
   $5b9cca4f, $682e6ff3, $748f82ee, $78a5636f, $84c87814, $8cc70208,
   $90befffa, $a4506ceb, $bef9a3f7, $c67178f2]);

procedure TSHA256.Compress; var W: TSHA256Buffer; H: TSHAHash = Hash; i, t1, t2: integer; begin for i := 0 to 15 do W[i]:= (((Buffer[i*4] shl 24)shr 0)or(Buffer[i*4+1] shl 16)or (Buffer[i*4+2] shl 8)or Buffer[i*4+3]) shr 0; for i := 16 to 63 do W[i] := ((((W[i-2]shr 17)or(W[i-2]shl 15))xor((W[i-2]shr 19)or(W[i-2]shl 13)) xor (W[i-2]shr 10))+W[i-7]+(((W[i-15]shr 7)or(W[i-15]shl 25)) xor ((W[i-15]shr 18)or(W[i-15]shl 14))xor(W[i-15]shr 3))+W[i-16])shr 0; for i := 0 to high(W) do begin t1 := (H.H+(((H.E shr 6)or(H.E shl 26))xor((H.E shr 11)or(H.E shl 21))xor ((H.E shr 25)or(H.E shl 7)))+((H.E and H.F)xor(not H.E and H.G))+K[i]+W[i])shr 0; t2 := ((((H.A shr 2)or(H.A shl 30))xor((H.A shr 13)or(H.A shl 19))xor ((H.A shr 22)xor(H.A shl 10)))+((H.A and H.B)xor(H.A and H.C)xor(H.B and H.C))) shr 0; H.H := H.G; H.G := H.F; H.F := H.E; H.E := (H.D+t1)shr 0; H.D := H.C; H.C := H.B; H.B := H.A; H.A := (t1+t2)shr 0; end; Hash.A := (Hash.A+H.A)shr 0; Hash.B := (Hash.B+H.B)shr 0; Hash.C := (Hash.C+H.C)shr 0; Hash.D := (Hash.D+H.D)shr 0; Hash.E := (Hash.E+H.E)shr 0; Hash.F := (Hash.F+H.F)shr 0; Hash.G := (Hash.G+H.G)shr 0; Hash.H := (Hash.H+H.H)shr 0; end;

A K: TSHA256Buffer constant table is used. Note the non standard definition of DWS for a const array: it will use .. = ([...]); instead of .. = (); as in classical Object Pascal.

The hash is computed from an internal W[] array, which is filled with the binary representation of the supplied Buffer[] bytes. That is, Buffer: array[0..63] of byte is first un-serialized in W: array[0..15] of cardinal.

Then the SHA-256 algorithm is performed in its most simple rolled version. An un-rolled version is not mandatory here, in our managed JavaScript runtime environment.

The only non obvious part of the above code is the use of ... shr 0 to enforce only positive 32 bit integers (aka cardinal) are used during the computation.

This small article tries to demonstrate how Smart, even if only in beta phase, may be a good candidate for building Rich AJAX client, and embed some low-level logic in a pure JavaScript application, using "classic" object pascal coding.
Some points are to be take in account, but conversion of existing code is very easy.

Feedback and comments are welcome in our forum, just as usual.