Thursday, May 20, 2010

Sampler: GDBM OO wrapper

 
unit Unit1; 
 
{ (c) 2010 bflm, contact: befelemepeseveze at Google's free mail
 
  Free sample code - copy, use, modify, distribute, sell, release under other license, ...
 
}
 
{$mode objfpc}{$h+}
 
interface
 
const
  DEFAULT_FMODE = 432; // =0660(8)
 
type
 
  { TGDBM }
 
  TGDBM = class
  private
    FCacheSize: Integer;
    FCentFree: Longbool;
    FCoalesceBlks: Longbool;
    FDBF: Pointer;
    FSyncMode: Longbool;
    function GetFDesc: Integer;
    procedure Open(Name: String; OpenRW: Integer; const Sync, NoLock: Boolean;
      BlockSize: Integer; Mode: Integer);
    procedure SetCacheSize(const AValue: Integer);
    procedure SetCentFree(const AValue: Longbool);
    procedure SetCoalesceBlks(const AValue: Longbool);
    procedure SetSyncMode(const AValue: Longbool);
  public
    constructor CreateNewDB(const Name: String; const Sync: Boolean = False;
      const NoLock: Boolean = False; const BlockSize: Integer = 0;
      const FMode: Integer = DEFAULT_FMODE);
    constructor CreateOrOpen(const Name: String; const Sync: Boolean = False;
      const NoLock: Boolean = False; const BlockSize: Integer = 0;
      const FMode: Integer = DEFAULT_FMODE);
    constructor CreateReader(const Name: String);
    constructor CreateWriter(const Name: String; const Sync: Boolean = False;
      const NoLock: Boolean = False);
    destructor Destroy; override;
    { True if Key deleted }
    function Delete(const Key: String): Boolean;
    { True if Key deleted }
    function Delete(const Key; const KeyLen: Integer): Boolean;
    function Exists(const Key: String): Boolean;
    function Exists(const Key; const KeyLen: Integer): Boolean;
    { True if Key found => Value set }
    function Fetch(const Key: String; out Value: String): Boolean;
    { True if Key found => Value set. Value MUST be disposed using C free. }
    function Fetch(const Key; const KeyLen: Integer; out Value: Pointer;
      out Len: Integer): Boolean;
    { True if FirstKey returned }
    function FirstKey(out Key: String): Boolean;
    { True if FirstKey returned }
    function FirstKey(out Key: Pointer; out Len: Integer): Boolean;
    { True if Key already existed => new Value NOT stored }
    function Insert(const Key, Value: String): Boolean;
    { True if Key already existed => new Value NOT stored }
    function Insert(const Key; const KeyLen: Integer; const Value;
      const ValueLen: Integer): Boolean;
    { True if Next returned }
    function NextKey(const Key: String; var Next: String): Boolean;
    { True if Next returned. Key MUST be disposed using C free. }
    function NextKey(const Key; const KeyLen: Integer; var Next: Pointer;
      var Len: Integer): Boolean;
    procedure Reorganize;
    procedure Replace(const Key, Value: String);
    procedure Replace(const Key; const KeyLen: Integer; const Value;
      const ValueLen: Integer);
    procedure Sync;
    property CacheSize: Integer read FCacheSize Write SetCacheSize;
    property CentFree: Longbool read FCentFree Write SetCentFree;
    property CoalesceBlks: Longbool read FCoalesceBlks Write SetCoalesceBlks;
    property FDesc: Integer read GetFDesc;
    property SyncMode: Longbool read FSyncMode Write SetSyncMode;
  end;
 
implementation
 
uses
  SysUtils,
  gdbm;
 
procedure CFree(P: pointer); cdecl; external 'c' Name 'free';
 
function Datum(const Buf: Pointer; const Len: Integer): TDatum; inline;
begin
  Result.dptr := Buf;
  Result.dsize := Len;
end;
 
procedure Fail;
begin
  raise Exception.Create(gdbm_strerror(gdbm_errno));
end;
 
function CheckError(const Ret: Integer): Integer;
begin
  if Ret <> 0 then
    Fail;
  Result := Ret;
end;
 
operator := (const Datum: TDatum): String;
begin
  if Datum.dptr <> nil then begin
    SetLength(Result, Datum.dsize);
    Move(Datum.dptr^, Result[1], Datum.dsize);
    CFree(Datum.dptr);
  end else
    Result := '';
end;
 
operator := (const S: String): TDatum;
begin
  Assert(S <> '');
  Result.dptr := PChar(S);
  Result.dsize := Length(S);
end;
 
{ TGDBM }
 
constructor TGDBM.CreateNewDB(const Name: String; const Sync, NoLock: Boolean;
  const BlockSize: Integer = 0; const FMode: Integer = DEFAULT_FMODE);
begin
  inherited Create;
  Open(Name, GDBM_NEWDB, Sync, NoLock, BlockSize, FMode);
end;
 
constructor TGDBM.CreateOrOpen(const Name: String; const Sync, NoLock: Boolean;
  const BlockSize: Integer; const FMode: Integer);
begin
  inherited Create;
  Open(Name, GDBM_WRCREAT, Sync, NoLock, BlockSize, FMode);
end;
 
constructor TGDBM.CreateReader(const Name: String);
begin
  inherited Create;
  Open(Name, GDBM_READER, False, False, 0, 0);
end;
 
constructor TGDBM.CreateWriter(const Name: String; const Sync, NoLock: Boolean);
begin
  inherited Create;
  Open(Name, GDBM_WRITER, Sync, NoLock, 0, 0);
end;
 
destructor TGDBM.Destroy;
begin
  if FDBF <> nil then
    gdbm_close(FDBF);
  inherited;
end;
 
function TGDBM.Delete(const Key: String): Boolean;
var
  K: TDatum;
begin
  K := Key;
  Result := gdbm_delete(FDBF, K) = 0;
end;
 
function TGDBM.Delete(const Key; const KeyLen: Integer): Boolean;
begin
  Result := gdbm_delete(FDBF, Datum(@Key, KeyLen)) = 0;
end;
 
function TGDBM.Exists(const Key: String): Boolean;
var
  K: TDatum;
begin
  K := Key;
  Result := gdbm_exists(FDBF, K) <> 0;
end;
 
function TGDBM.Exists(const Key; const KeyLen: Integer): Boolean;
begin
  Result := gdbm_exists(FDBF, Datum(@Key, KeyLen)) <> 0;
end;
 
function TGDBM.Fetch(const Key: String; out Value: String): Boolean;
var
  K, V: TDatum;
begin
  K := Key;
  V := gdbm_fetch(FDBF, K);
  if V.dptr <> nil then begin
    Value := V;
    Exit(True);
  end;
  Result := False;
end;
 
function TGDBM.Fetch(const Key; const KeyLen: Integer; out Value: Pointer;
  out Len: Integer): Boolean;
var
  V: TDatum;
begin
  V := gdbm_fetch(FDBF, Datum(@Key, KeyLen));
  if V.dptr = nil then
    Exit(False);
  Value := V.dptr;
  Len := V.dsize;
  Result := True;
end;
 
function TGDBM.FirstKey(out Key: String): Boolean;
var
  K: TDatum;
begin
  K := gdbm_firstkey(FDBF);
  if K.dptr <> nil then begin
    Key := K;
    Exit(True);
  end;
  Result := False;
end;
 
function TGDBM.FirstKey(out Key: Pointer; out Len: Integer): Boolean;
var
  K: TDatum;
begin
  K := gdbm_firstkey(FDBF);
  if K.dptr <> nil then begin
    Key := K.dptr;
    Len := K.dsize;
    Exit(True);
  end;
  Result := False;
end;
 
procedure TGDBM.Open(Name: String; OpenRW: Integer; const Sync, NoLock: Boolean;
  BlockSize: Integer; Mode: Integer);
begin
  Assert(Name <> '');
  FSyncMode := Sync;
  if SyncMode then
    OpenRW += GDBM_DOSYNC;
  if NoLock then;
  OpenRW += GDBM_NOLOCK;
  FDBF := gdbm_open(PChar(Name), BlockSize, OpenRW, Mode, nil);
  if FDBF = nil then
    Fail;
end;
 
function TGDBM.GetFDesc: Integer;
begin
  Result := gdbm_fdesc(FDBF);
end;
 
procedure TGDBM.SetCacheSize(const AValue: Integer);
var
  Ret: Integer;
begin
  if FCacheSize = AValue then
    exit;
  FCacheSize := AValue;
  Ret := gdbm_setopt(FDBF, GDBM_CACHESIZE, @FCacheSize, SizeOf(FCacheSize));
  if Ret <> 0 then
    Fail;
end;
 
procedure TGDBM.SetCentFree(const AValue: Longbool);
begin
  if FCentFree = AValue then
    exit;
  FCentFree := AValue;
  CheckError(gdbm_setopt(FDBF, GDBM_CENTFREE, @FCentFree, SizeOf(FCentFree)));
end;
 
procedure TGDBM.SetCoalesceBlks(const AValue: Longbool);
begin
  if FCoalesceBlks = AValue then
    exit;
  FCoalesceBlks := AValue;
  CheckError(gdbm_setopt(FDBF, GDBM_COALESCEBLKS, @FCoalesceBlks,
    SizeOf(FCoalesceBlks)));
end;
 
procedure TGDBM.SetSyncMode(const AValue: Longbool);
begin
  if FSyncMode = AValue then
    exit;
  FSyncMode := AValue;
  CheckError(gdbm_setopt(FDBF, GDBM_SYNCMODE, @FSyncMode, SizeOf(FSyncMode)));
end;
 
function TGDBM.Insert(const Key, Value: String): Boolean;
var
  K, V: TDatum;
  Ret: Integer;
begin
  K := Key;
  V := Value;
  Ret := gdbm_store(FDBF, K, V, GDBM_INSERT);
  ;
  if Ret < 0 then // reader attempts insert
    Fail;
  Result := Ret <> 0;
end;
 
function TGDBM.Insert(const Key; const KeyLen: Integer; const Value;
  const ValueLen: Integer): Boolean;
var
  Ret: Integer;
begin
  Ret := gdbm_store(FDBF, Datum(@Key, KeyLen), Datum(@Value, ValueLen), GDBM_INSERT);
  if Ret < 0 then // reader attempts insert
    Fail;
  Result := Ret <> 0;
end;
 
function TGDBM.NextKey(const Key: String; var Next: String): Boolean;
var
  K, N: TDatum;
begin
  K := Key;
  N := gdbm_nextkey(FDBF, K);
  if N.dptr <> nil then begin
    Next := N;
    Exit(True);
  end;
  Result := False;
end;
 
function TGDBM.NextKey(const Key; const KeyLen: Integer; var Next: Pointer;
  var Len: Integer): Boolean;
var
  N: TDatum;
begin
  N := gdbm_nextkey(FDBF, Datum(@Key, KeyLen));
  if N.dptr <> nil then begin

    Next := N.dptr;
    Len := N.dsize;
    Exit(True);
  end;
  Result := False;
end;
 
procedure TGDBM.Reorganize;
var
  Ret: Integer;
begin
  Ret := gdbm_reorganize(FDBF);
  if Ret <> 0 then
    Fail;
end;
 
procedure TGDBM.Replace(const Key, Value: String);
var
  K, V: TDatum;
begin
  K := Key;
  V := Value;
  CheckError(gdbm_store(FDBF, K, V, GDBM_REPLACE));
end;
 
procedure TGDBM.Replace(const Key; const KeyLen: Integer; const Value;
  const ValueLen: Integer);
begin
  CheckError(gdbm_store(FDBF, Datum(@Key, KeyLen), Datum(@Value, ValueLen),
    GDBM_REPLACE));
end;
 
procedure TGDBM.Sync;
begin
  gdbm_sync(FDBF);
end;
 
end.
 

Monday, May 10, 2010

Sampler: Generic enumerators for generic containers

unit Unit1; 

{ (c) 2010 bflm, contact: befelemepeseveze at Google's free mail

  Free sample code - copy, use, modify, distribute, sell, release under other license, ...

}

{$mode objfpc}{$H+}

interface

type

  { TEnumerator }

  generic TEnumerator<TValue, TIterator> = object
  public type
    TGetCurrent = function(const Iterator: TIterator): TValue of object;
    TMoveNext = function(var Iterator: TIterator): Boolean of object;
  private var
    FGetCurrent: TGetCurrent;
    FMoveNext: TMoveNext;
    FIterator: TIterator;
    function GetCurrent: TValue;
  public
    procedure Init(const InitialIterator: TIterator; const Mover: TMoveNext; const Getter: TGetCurrent);
    function MoveNext: Boolean;
    property Current: TValue read GetCurrent;
  end;

  { TVector }

  generic TVector<T> = object
  public type
    TData = array of T;
    TCompare = function(const A, B: T): Integer;
    TVectorEnumerator = specialize TEnumerator<T, Integer>;
  private var
    FData: TData;
    FLen: Integer;
  private
    function GetCap: Integer;
    function GetData: TData;
    function GetItems(const Index: Integer): T; inline;
    function GetLen: Integer; inline;
    function MoveNext(var Index: Integer): Boolean;
    procedure Sort(Left, Right: Integer; const Compare: TCompare);
    procedure SetCap(const AValue: Integer);
    procedure SetData(const AValue: TData);
    procedure SetItems(const Index: Integer; const AValue: T); inline;
    procedure SetLen(const AValue: Integer);
  public
    function GetEnumerator: TVectorEnumerator;
    function Add(const Value: T): Integer;
    procedure Clear;
    procedure Pack;
    procedure Sort(const Compare: TCompare);
    property Cap: Integer read GetCap write SetCap;
    property Data: TData read GetData write SetData;
    property Items[const Index: Integer]: T read GetItems write SetItems; default;
    property Len: Integer read GetLen write SetLen;
  end;

implementation

{ TEnumerator }

function TEnumerator.GetCurrent: TValue;
begin
  Result := FGetCurrent(FIterator);
end;

function TEnumerator.MoveNext: Boolean;
begin
  Result := FMoveNext(FIterator);
end;

procedure TEnumerator.Init(const InitialIterator: TIterator; const Mover: TMoveNext; const Getter: TGetCurrent);
begin
  Assert(Assigned(Mover));
  Assert(Assigned(Getter));
  FIterator := InitialIterator;
  FMoveNext := Mover;
  FGetCurrent := Getter;
end;

{ TVector }

function TVector.Add(const Value: T): Integer;
var NeedCap: Integer;
begin
  NeedCap := Len + 1;
  if NeedCap > Cap then
    Cap := 2 * NeedCap;
  Result := FLen;
  Inc(FLen);
  Items[Result] := Value;
end;

procedure TVector.Clear;
begin
  Cap := 0;
end;

function TVector.GetItems(const Index: Integer): T;
begin
  Result := FData[Index];
end;

function TVector.GetCap: Integer;
begin
  Result := Length(FData);
end;

function TVector.GetData: TData;
begin
  Result := Copy(FData, 0, Len);
end;

function TVector.GetEnumerator: TVectorEnumerator;
begin
  Result.Init(-1, @MoveNext, @GetItems);
end;

function TVector.GetLen: Integer;
begin
  if FData = nil then
    FLen := 0; // boot
  Result := FLen;
end;

function TVector.MoveNext(var Index: Integer): Boolean;
begin
  Result := Index + 1 < Len;
  if Result then
    Inc(Index);
end;

procedure TVector.SetCap(const AValue: Integer);
begin
  if AValue = Cap then
    Exit;
  if Len > AValue then
    FLen := AValue;
  SetLength(FData, AValue);
end;

procedure TVector.SetData(const AValue: TData);
begin
  FData := AValue;
  FLen := Cap;
end;

procedure TVector.SetItems(const Index: Integer; const AValue: T);
begin
  FData[Index] := AValue;
end;

procedure TVector.SetLen(const AValue: Integer);
begin
  if AValue = Len then
    Exit;
  if AValue > Cap then
    Cap := AValue
  else if AValue <= Cap div 2 then
    Cap := Cap div 2;
  FLen := AValue;
end;

procedure TVector.Sort(Left, Right: Integer; const Compare: TCompare);
var
  L, R: Integer;
  Pivot, Swap: T;
begin
 repeat
   L := Left;
   R := Right;
   Pivot := Items[(Left + Right) div 2];
   repeat
     while Compare(Pivot, Items[L]) > 0 do
       L += 1;
     while Compare(Pivot, Items[R]) < 0 do
       R -= 1;
     if L <= R then begin
       Swap := Items[L];
       Items[L] := Items[R];
       Items[R] := Swap;
       L += 1;
       R -= 1;
     end;
   until L > R;
   if Left < R then
     Sort(Left, R, Compare);
   Left := L;
 until L >= Right;
end;

procedure TVector.Pack;
begin
  Cap := Len;
end;

procedure TVector.Sort(const Compare: TCompare);
begin
  if Len > 1 then
    Sort(0, FLen - 1, Compare);
end;

end.

program project1;

{$mode objfpc}{$H+}

uses Unit1;

type
  TIntVector = specialize TVector<Integer>;

function IntCmp(const A, B: Integer): Integer;
begin
  Result := A - B;
end;

procedure Dump(const Vector: TIntVector);
var V: Integer;
begin
  Write(Vector.Len, ' item(s): ');
  for V in Vector do
    Write(V, ' ');
  Writeln;
end;

procedure Test(const N, X: Integer);
var
  Vector: TIntVector;
  I: Integer;
begin
  for I := 1 to N do
    Vector.Add(I xor X);
  Dump(Vector);
  Vector.Sort(@IntCmp);
  Dump(Vector);
  WriteLn;
end;

begin // main
  {$if declared(HaltOnNotReleased)}
  HaltOnNotReleased := True;
  {$endif}
  Test(0, 0);
  Test(1, 0);
  Test(11, 0);
  Test(13, $55);
  Test(17, $AA);
  Test(19, $FF);
end.

$ fpc -B -gh project1 && ./project1 
Free Pascal Compiler version 2.5.1 [2010/05/05] for x86_64
Copyright (c) 1993-2009 by Florian Klaempfl
Target OS: Linux for x86-64
Compiling project1.pas
Compiling unit1.pas
Linking project1
/usr/bin/ld: warning: link.res contains output sections; did you forget -T?
259 lines compiled, 0.1 sec 
0 item(s): 
0 item(s): 

1 item(s): 1 
1 item(s): 1 

11 item(s): 1 2 3 4 5 6 7 8 9 10 11 
11 item(s): 1 2 3 4 5 6 7 8 9 10 11 

13 item(s): 84 87 86 81 80 83 82 93 92 95 94 89 88 
13 item(s): 80 81 82 83 84 86 87 88 89 92 93 94 95 

17 item(s): 171 168 169 174 175 172 173 162 163 160 161 166 167 164 165 186 187 
17 item(s): 160 161 162 163 164 165 166 167 168 169 171 172 173 174 175 186 187 

19 item(s): 254 253 252 251 250 249 248 247 246 245 244 243 242 241 240 239 238 237 236 
19 item(s): 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 

Heap dump by heaptrc unit
11 memory blocks allocated : 840/840
11 memory blocks freed     : 840/840
0 unfreed memory blocks : 0
True heap size : 98304
True free heap : 98304
$