Ver Fonte

Merged revisions 11017,11032-11033,11042,11044,11046-11047,11053,11060,11062,11064,11067,11075,11078,11080,11085,11089,11094,11096,11098,11103-11104,11106,11108-11109,11111,11114,11117,11122,11124,11126,11130-11131,11133,11136,11139-11141,11146-11147,11152-11154,11157,11159,11166-11167,11170,11173,11178,11181-11182,11184-11185,11187-11189,11195-11196,11199,11206-11209,11214-11215,11223,11225,11227,11232,11235,11239-11240,11249-11256,11258,11260,11264-11265,11271,11278,11280-11282,11286-11288,11292-11294,11297,11299-11300,11302,11304-11311,11313-11316,11318-11319,11324-11326,11328-11333,11335-11336,11339-11340,11346-11347,11349,11362,11369,11371-11375,11393-11396,11401,11411-11414,11420,11422,11427-11428,11465,11469-11470,11487-11488,11490,11518-11521,11523,11528,11535,11551,11553,11555,11557,11562,11564,11571,11588,11616,11619,11621-11622,11628,11664-11667,11669-11670,11673-11675,11679,11681,11683,11685-11686,11689-11692,11694-11696,11698,11701-11702,11705-11707,11712-11718,11723-11726,11728-11729,11733-11737,11747,11778,11780-11781,11785,11800,11810,11822,11831,11836,11848,11865,11872,11876-11878,11881-11883,11889,11891-11896,11899-11902,11920-11925,11931-11932,11935,11938,11941-11942,11986,11990,11992,12010-12011,12014,12018,12026 via svnmerge from
svn+ssh://svn.freepascal.org/FPC/svn/fpc/trunk

........
r11017 | michael | 2008-05-19 21:08:15 +0200 (Mon, 19 May 2008) | 1 line

* Patch from Bartosz Bogacz to be able to parse streams without size
........
r11047 | michael | 2008-05-23 09:52:04 +0200 (Fri, 23 May 2008) | 1 line

* UTF8 strings read as normal strings, not widestrings (bug reported by Stefan Lechner)
........
r11199 | michael | 2008-06-05 09:06:47 +0200 (Thu, 05 Jun 2008) | 1 line

* Patch from Joao Morais to fix the case where only a time is provided
........
r11314 | michael | 2008-07-02 20:00:07 +0200 (Wed, 02 Jul 2008) | 1 line

* Changed var to out parameters for strtoint*
........
r11616 | michael | 2008-08-21 09:04:18 +0200 (Thu, 21 Aug 2008) | 1 line

* Finished support for sequences and domains
........
r11669 | michael | 2008-08-30 23:15:04 +0200 (Sat, 30 Aug 2008) | 1 line

* Added RunArguments
........
r11673 | michael | 2008-08-31 17:01:05 +0200 (Sun, 31 Aug 2008) | 1 line

* Patch so uninitialized sockets are not closed
........
r11674 | michael | 2008-08-31 22:14:05 +0200 (Sun, 31 Aug 2008) | 5 lines

* Fixed bug #0011528,
* StripQuotes now also does single quotes.
* Note that for TMemIniFile the previous behaviour (StripQuotes=False) is kept, which
is Delphi compatible.
........
r11675 | michael | 2008-08-31 22:22:21 +0200 (Sun, 31 Aug 2008) | 1 line

* Patch from Zolotov Alex to include apr_hash
........
r11679 | michael | 2008-08-31 23:57:27 +0200 (Sun, 31 Aug 2008) | 3 lines

* Patch from Martin Schreiber to fix streaming inherited collections
(mantis #11774)
........
r11681 | michael | 2008-09-01 11:17:58 +0200 (Mon, 01 Sep 2008) | 3 lines

* Added overloaded call for fplstat which has var argument (as for stat)
* Implemented support for faSymlink, as per 9915
........
r11686 | michael | 2008-09-01 18:06:28 +0200 (Mon, 01 Sep 2008) | 2 lines

* Implemented StrictDelimiter without the delphi bugs (mantis #8910)
........
r11747 | michael | 2008-09-11 20:47:41 +0200 (Thu, 11 Sep 2008) | 9 lines

* Bugfixes from Attila Borka:
- Bug fix in the template parser. Bug0012095
- Fixed: CGI applications did not create and populate contentfields (caused AV if someone
tried to access it) for the http request, both the query and content parameters were put
into the queryfields list
- Bug0012094 fix: CGI applications AllowDefaultModule=true did not work for Delphi style calls (it is ok
for querystring parameter passed module names)
- fptemplate.pp->TTemplateParser: Added support for template tag parameters.
........
r11800 | michael | 2008-09-18 08:44:41 +0200 (Thu, 18 Sep 2008) | 1 line

* Partially applied the patch from Attila Borka, added UseStreaming class method so datamodules without streamed content can be used (bug ID 12158)
........
r11865 | michael | 2008-10-05 16:37:01 +0200 (Sun, 05 Oct 2008) | 1 line

* Initial check-in
........
r11896 | michael | 2008-10-13 16:17:06 +0200 (Mon, 13 Oct 2008) | 4 lines

* Override EnableIgnore in TDecorator
* Pass EnableIgnore to TTest when a test is added to a TSuite
........
r11942 | michael | 2008-10-22 14:25:12 +0200 (Wed, 22 Oct 2008) | 24 lines

* Patch from Sergei Gorelkin
xmlutils.pp:
+ Added THashTable - a simple hashed container with WideString keys.

dom.pp:
* Use the hash table instead of a sorted list for storing document IDs.
* Replaced all TLists by TFPList (which is smaller and faster).
* Fixed TDOMElement.RemoveAttributeNode to throw NOT_FOUND_ERR when
the requested node is not one of the element's attributes.
+ Added node read-only checks where required by the specs, this fixes
about 50 DOM tests.

xmlread.pp:

* Got rid of TXMLCharSource.FReloadHook, the corresponding procedure may
be called directly.
* Used a separate buffer to store the entity value literals, this
enables correct including of external PEs that have a text declaration
at the beginning.
* Some refactoring: ParseAttribute has been split into a separate
procedure, ProcessTextAndRefs was merged into ParseContent.
........
r11990 | michael | 2008-10-28 16:40:47 +0100 (Tue, 28 Oct 2008) | 1 line

* Added TIFF reader/Writer from Mattias gaertner
........
r12010 | michael | 2008-11-01 23:15:23 +0100 (Sat, 01 Nov 2008) | 1 line

* Added expression parser
........
r12011 | michael | 2008-11-01 23:28:04 +0100 (Sat, 01 Nov 2008) | 1 line

* Some more explanations
........
r12026 | michael | 2008-11-04 19:33:05 +0100 (Tue, 04 Nov 2008) | 17 lines

* Patch from Sergei Gorelkin:
src/xmlread.pp, src/dom.pp
* Improvements to attribute processing: attributes are now validated as
they come. This enables reporting of the corresponding validation
errors at correct positions (previously everything was reported at the
end of element start-tag).
* Search for a declaration for attribute, not for an attribute
corresponding to the declaration. This reduces number of lookups
(because unspecified attributes are not searched) and obsoletes the
need in FDeclared field on every attribute.

tests/domunit.pp, tests/testgen.pp:

* Various improvements required to support converting of the
DOM level 3 XPath module.
........

git-svn-id: branches/fixes_2_2@12083 -

michael há 17 anos atrás
pai
commit
106e44853a
44 ficheiros alterados com 14190 adições e 596 exclusões
  1. 8 0
      .gitattributes
  2. 633 0
      packages/cdrom/src/fpcddb.pp
  3. 62 58
      packages/fcl-base/Makefile
  4. 2 1
      packages/fcl-base/Makefile.fpc
  5. 140 0
      packages/fcl-base/examples/fpexprpars.txt
  6. 5999 0
      packages/fcl-base/examples/testexprpars.pp
  7. 1 0
      packages/fcl-base/fpmake.pp
  8. 2 0
      packages/fcl-base/src/daemonapp.pp
  9. 3406 0
      packages/fcl-base/src/fpexprpars.pp
  10. 3 1
      packages/fcl-base/src/inifiles.pp
  11. 3 0
      packages/fcl-base/src/win/daemonapp.inc
  12. 168 23
      packages/fcl-db/src/datadict/fpdatadict.pp
  13. 3 3
      packages/fcl-db/src/datadict/fpdddiff.pp
  14. 343 51
      packages/fcl-db/src/datadict/fpddfb.pp
  15. 7 0
      packages/fcl-db/src/datadict/fpddsqldb.pp
  16. 1 0
      packages/fcl-fpcunit/src/fpcunit.pp
  17. 13 0
      packages/fcl-fpcunit/src/testdecorator.pp
  18. 59 59
      packages/fcl-image/Makefile
  19. 1 1
      packages/fcl-image/Makefile.fpc
  20. 17 0
      packages/fcl-image/fpmake.pp
  21. 1272 0
      packages/fcl-image/src/fpreadtiff.pas
  22. 222 0
      packages/fcl-image/src/fptiffcmn.pas
  23. 681 0
      packages/fcl-image/src/fpwritetiff.pas
  24. 3 0
      packages/fcl-net/src/ssockets.pp
  25. 164 0
      packages/fcl-web/fptemplate.txt
  26. 3 3
      packages/fcl-web/src/custcgi.pp
  27. 5 2
      packages/fcl-web/src/fpapache.pp
  28. 16 3
      packages/fcl-web/src/fpcgi.pp
  29. 9 0
      packages/fcl-web/src/fphttp.pp
  30. 255 70
      packages/fcl-web/src/fptemplate.pp
  31. 13 10
      packages/fcl-web/src/httpdefs.pp
  32. 100 84
      packages/fcl-xml/src/dom.pp
  33. 201 175
      packages/fcl-xml/src/xmlread.pp
  34. 221 0
      packages/fcl-xml/src/xmlutils.pp
  35. 6 0
      packages/fcl-xml/tests/domunit.pp
  36. 69 34
      packages/fcl-xml/tests/testgen.pp
  37. 7 0
      packages/httpd20/src/apr/apr.pas
  38. 5 0
      packages/httpd22/src/apr/apr.pas
  39. 28 6
      rtl/objpas/sysutils/dati.inc
  40. 3 3
      rtl/objpas/sysutils/sysstr.inc
  41. 3 3
      rtl/objpas/sysutils/sysstrh.inc
  42. 12 0
      rtl/unix/bunxovl.inc
  43. 2 0
      rtl/unix/bunxovlh.inc
  44. 19 6
      rtl/unix/sysutils.pp

+ 8 - 0
.gitattributes

@@ -899,6 +899,7 @@ packages/cdrom/src/cdromioctl.pp svneol=native#text/plain
 packages/cdrom/src/cdromlin.inc svneol=native#text/plain
 packages/cdrom/src/cdromw32.inc svneol=native#text/plain
 packages/cdrom/src/discid.pp svneol=native#text/plain
+packages/cdrom/src/fpcddb.pp svneol=native#text/plain
 packages/cdrom/src/lincd.pp svneol=native#text/plain
 packages/cdrom/src/major.pp svneol=native#text/plain
 packages/cdrom/src/scsidefs.pp svneol=native#text/plain
@@ -980,6 +981,7 @@ packages/fcl-base/examples/dparser.pp svneol=native#text/plain
 packages/fcl-base/examples/dsockcli.pp svneol=native#text/plain
 packages/fcl-base/examples/dsocksvr.pp svneol=native#text/plain
 packages/fcl-base/examples/fpdoc.dtd svneol=native#text/plain
+packages/fcl-base/examples/fpexprpars.txt svneol=native#text/plain
 packages/fcl-base/examples/fstream.pp svneol=native#text/plain
 packages/fcl-base/examples/htdump.pp svneol=native#text/plain
 packages/fcl-base/examples/intl/Makefile svneol=native#text/plain
@@ -1024,6 +1026,7 @@ packages/fcl-base/examples/testbs.pp svneol=native#text/plain
 packages/fcl-base/examples/testcgi.html svneol=native#text/plain
 packages/fcl-base/examples/testcgi.pp svneol=native#text/plain
 packages/fcl-base/examples/testcont.pp svneol=native#text/plain
+packages/fcl-base/examples/testexprpars.pp svneol=native#text/plain
 packages/fcl-base/examples/testez.pp svneol=native#text/plain
 packages/fcl-base/examples/testhres.pp svneol=native#text/plain
 packages/fcl-base/examples/testnres.pp svneol=native#text/plain
@@ -1062,6 +1065,7 @@ packages/fcl-base/src/custapp.pp svneol=native#text/plain
 packages/fcl-base/src/daemonapp.pp svneol=native#text/plain
 packages/fcl-base/src/eventlog.pp svneol=native#text/plain
 packages/fcl-base/src/felog.inc svneol=native#text/plain
+packages/fcl-base/src/fpexprpars.pp svneol=native#text/plain
 packages/fcl-base/src/fptimer.pp svneol=native#text/plain
 packages/fcl-base/src/gettext.pp svneol=native#text/plain
 packages/fcl-base/src/go32v2/custapp.inc svneol=native#text/plain
@@ -1366,13 +1370,16 @@ packages/fcl-image/src/fpreadpcx.pas svneol=native#text/plain
 packages/fcl-image/src/fpreadpng.pp svneol=native#text/plain
 packages/fcl-image/src/fpreadpnm.pp svneol=native#text/plain
 packages/fcl-image/src/fpreadtga.pp svneol=native#text/plain
+packages/fcl-image/src/fpreadtiff.pas svneol=native#text/plain
 packages/fcl-image/src/fpreadxpm.pp svneol=native#text/plain
+packages/fcl-image/src/fptiffcmn.pas svneol=native#text/plain
 packages/fcl-image/src/fpwritebmp.pp svneol=native#text/plain
 packages/fcl-image/src/fpwritejpeg.pas svneol=native#text/plain
 packages/fcl-image/src/fpwritepcx.pas svneol=native#text/plain
 packages/fcl-image/src/fpwritepng.pp svneol=native#text/plain
 packages/fcl-image/src/fpwritepnm.pp svneol=native#text/plain
 packages/fcl-image/src/fpwritetga.pp svneol=native#text/plain
+packages/fcl-image/src/fpwritetiff.pas svneol=native#text/plain
 packages/fcl-image/src/fpwritexpm.pp svneol=native#text/plain
 packages/fcl-image/src/freetype.pp svneol=native#text/plain
 packages/fcl-image/src/freetypeh.pp svneol=native#text/plain
@@ -1492,6 +1499,7 @@ packages/fcl-registry/tests/testbasics.pp svneol=native#text/plain
 packages/fcl-web/Makefile svneol=native#text/plain
 packages/fcl-web/Makefile.fpc svneol=native#text/plain
 packages/fcl-web/fpmake.pp svneol=native#text/plain
+packages/fcl-web/fptemplate.txt svneol=native#text/plain
 packages/fcl-web/src/README svneol=native#text/plain
 packages/fcl-web/src/cgiapp.pp svneol=native#text/plain
 packages/fcl-web/src/custcgi.pp svneol=native#text/plain

+ 633 - 0
packages/cdrom/src/fpcddb.pp

@@ -0,0 +1,633 @@
+{
+    Copyright (c) 2008 by Michael Van Canneyt
+
+    Unit to parse CDDB responses and construct a list
+    of tracks in a CD.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpcddb;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils; 
+
+Type
+  TCDDisk = Class;
+
+  { TCDTrack }
+
+  TCDTrack = Class(TCollectionItem)
+  private
+    FDuration: TDateTime;
+    FExtra: String;
+    FPerformer: String;
+    FTitle: String;
+    function GetPerformer: String;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+  Published
+    Property Title : String Read FTitle Write FTitle;
+    Property Performer : String Read GetPerformer Write FPerformer;
+    Property Extra : String Read FExtra Write FExtra;
+    Property Duration : TDateTime Read FDuration Write FDuration;
+  end;
+
+  { TCDTracks }
+
+  TCDTracks = Class(TCollection)
+  private
+    FCDDisk: TCDDisk;
+    function GetT(AIndex : Integer): TCDTrack;
+    procedure SetT(AIndex : Integer; const AValue: TCDTrack);
+  Public
+    Property CDDisk : TCDDisk Read FCDDisk;
+    Function AddTrack(Const ATitle,AExtra : String; ADuration : TDateTime) : TCDTrack;
+    Function AddTrack(Const ATitle,AExtra : String) : TCDTrack;
+    Function AddTrack(Const ATitle : String) : TCDTrack;
+    Property Track[AIndex : Integer] : TCDTrack Read GetT Write SetT; default;
+  end;
+
+
+  { TCDDisk }
+
+  TCDDisk = Class(TCollectionItem)
+  private
+    FDiskID: Integer;
+    FExtra: String;
+    FPerformer: String;
+    FPlayOrder: String;
+    FTitle: String;
+    FTracks: TCDTracks;
+    FYear: Word;
+    function GetDiskID: String;
+    procedure SetDiskID(const AValue: String);
+    procedure SetTracks(const AValue: TCDTracks);
+  Protected
+    Function CreateTracks : TCDTracks; virtual;
+  Public
+    Constructor Create(ADiskID : Integer);
+    Constructor Create(ACollection : TCollection); override;
+    Procedure Assign(Source : TPersistent); override;
+    Property IntDiscID : Integer Read FDiskID Write FDiskID;
+  Published
+    Property PlayOrder : String Read FPlayOrder Write FPlayOrder;
+    Property Year : Word Read FYear Write FYear;
+    Property Title : String Read FTitle Write FTitle;
+    Property Performer : String Read FPerformer Write FPerformer;
+    Property Extra : String Read FExtra Write FExtra;
+    Property DiscID : String Read GetDiskID Write SetDiskID;
+    property Tracks : TCDTracks Read FTracks Write SetTracks;
+  end;
+
+  { TCDDisks }
+
+  TCDDisks = Class(TCollection)
+  private
+    function GetD(AIndex : Integer): TCDDisk;
+    procedure SetD(AIndex : Integer; const AValue: TCDDisk);
+  Public
+    Function AddDisk(ADiscID : String) : TCDDisk;
+    Function AddDisk : TCDDisk;
+    Property Disk[AIndex : Integer] : TCDDisk Read GetD Write SetD; default;
+  end;
+
+  { TCDDBQueryMatch }
+  TCDDBQueryMatch = Class(TCollectionItem)
+  private
+    FCategory: String;
+    FDiscID: Integer;
+    FPerformer: String;
+    FTitle: String;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+  Published
+    Property DiscID : Integer Read FDiscID Write FDiscID;
+    Property Category : String Read FCategory Write FCategory;
+    Property Title : String Read FTitle Write FTitle;
+    Property Performer : String Read FPerformer Write FPerformer;
+  end;
+
+  { TCDDBQueryMatches }
+
+  TCDDBQueryMatches = Class(TCollection)
+  private
+    function GetM(AIndex : Integer): TCDDBQueryMatch;
+    procedure SetM(AIndex : Integer; const AValue: TCDDBQueryMatch);
+  Public
+    Function AddMatch(Const ADiscID: Integer; Const ACategory,ATitle, APerformer : String) : TCDDBQueryMatch;
+    Function AddMatch(Const ADiscID,ACategory,ATitle, APerformer : String) : TCDDBQueryMatch;
+    Function AddMatch : TCDDBQueryMatch;
+    Property Match[AIndex : Integer] :TCDDBQueryMatch Read GetM Write SetM; default;
+  end;
+  { TCDDBParser }
+
+  TCDDBParser = Class(TComponent)
+  private
+    FDisks: TCDDisks;
+    FDisk : TCDDisk;
+    function ParseExtraDiskData(AData: String): Boolean;
+    function ParseExtraTrackData(ATrack: TCDTrack; AData: String): Boolean;
+    procedure SetDisks(const AValue: TCDDisks);
+    procedure SplitQueryResponse(AResponse: String; var ACategory, ADiscID, ATitle, APerformer: String);
+    procedure SplitTitle(const ALine: String; var AArtist, ATitle: String;
+      PreferTitle: boolean);
+    function StdReplacements(S: String): String;
+  Protected
+    Procedure CheckDisk;
+    function CheckCDDBCmdResult(var S: String): Integer;
+    Function CreateDisks :TCDDisks; virtual;
+    Function IsComment(Const L : String) : Boolean;
+    Function GetTrack(Const TrackNo : Integer) : TCDTrack;
+    Property Disk : TCDDisk Read FDisk;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    Function ParseCDDBReadResponse(Response : TStrings; WithHeader : Boolean = True) : Integer;
+    Function ParseCDDBReadResponse(Response : TStream; WithHeader : Boolean = True) : Integer;
+    Function ParseCDDBQueryResponse(Response : TStrings; Matches : TCDDBQueryMatches; WithHeader : Boolean = True) : Integer;
+    Function ParseCDDBQueryResponse(Response : TStream; Matches : TCDDBQueryMatches; WithHeader : Boolean = True) : Integer;
+  Published
+    Property Disks : TCDDisks Read FDisks Write SetDisks;
+  end;
+
+  ECDDBParser = Class(Exception);
+
+Function DiscIDToStr(ID : Integer) : String;
+Function StrToDiscID(S : String) : Integer;
+
+implementation
+
+Resourcestring
+  SErrNoDisk         = 'No disk active';
+  SErrInvalidTrackNo = 'Invalid track number: %d';
+  SErrParsingLine    = 'An error occured while parsing line %d of the response: %s';
+  SErrCDDBResponse   = 'CDDB error in command response: %s';
+
+function DiscIDToStr(ID: Integer): String;
+begin
+  Result:=LowerCase(Format('%.8x',[ID]));
+end;
+
+function StrToDiscID(S: String): Integer;
+begin
+  Result:=StrToIntDef('$'+S,-1);
+end;
+
+{ TCDTrack }
+
+function TCDTrack.GetPerformer: String;
+begin
+  Result:=FPerformer;
+  If (Result='') and Assigned(Collection) and (Collection is TCDTracks) then
+    If Assigned(TCDTracks(Collection).CDDisk) then
+      Result:=TCDTracks(Collection).CDDisk.Performer;
+end;
+
+procedure TCDTrack.Assign(Source: TPersistent);
+
+Var
+  T : TCDTrack;
+
+begin
+  if (Source is TCDTrack) then
+    begin
+    T:=Source as TCDTrack;
+    FTitle:=T.FTitle;
+    FExtra:=T.FExtra;
+    FPerformer:=T.FPerformer;
+    FDuration:=T.FDuration;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+{ TCDDisk }
+
+procedure TCDDisk.SetTracks(const AValue: TCDTracks);
+begin
+  if FTracks=AValue then exit;
+  FTracks.Assign(AValue);
+end;
+
+function TCDDisk.GetDiskID: String;
+begin
+  Result:=DiscIDToStr(FdiskID);
+end;
+
+procedure TCDDisk.SetDiskID(const AValue: String);
+begin
+  FDiskID:=StrToDiscID(AValue);
+end;
+
+function TCDDisk.CreateTracks: TCDTracks;
+begin
+ Result:=TCDTracks.Create(TCDTrack);
+end;
+
+constructor TCDDisk.Create(ADiskID: Integer);
+begin
+  FDiskID:=ADiskID;
+  Create(Nil);
+end;
+
+constructor TCDDisk.Create(ACollection: TCollection);
+begin
+  FTracks:=CreateTracks;
+  FTracks.FCDDisk:=Self;
+  inherited Create(ACollection);
+end;
+
+procedure TCDDisk.Assign(Source: TPersistent);
+
+Var
+  D : TCDDisk;
+
+begin
+  if Source is TCDDisk then
+    begin
+    D:=Source as TCDDisk;
+    FTitle:=D.FTitle;
+    FExtra:=D.FExtra;
+    FPerformer:=D.FPerformer;
+    FYear:=D.FYear;
+    FTracks.Assign(D.FTracks);
+    FPLayOrder:=D.FPlayOrder;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+{ TCDTracks }
+
+function TCDTracks.GetT(AIndex : Integer): TCDTrack;
+begin
+  Result:=Items[AIndex] as TCDTrack;
+end;
+
+procedure TCDTracks.SetT(AIndex : Integer; const AValue: TCDTrack);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TCDTracks.AddTrack(const ATitle, AExtra: String; ADuration: TDateTime
+  ): TCDTrack;
+begin
+  Result:=Add as TCDTrack;
+  Result.Title:=ATitle;
+  Result.Extra:=AExtra;
+  Result.Duration:=ADuration;
+end;
+
+function TCDTracks.AddTrack(const ATitle, AExtra: String): TCDTrack;
+begin
+  Result:=AddTrack(ATitle,AExtra,0);
+end;
+
+function TCDTracks.AddTrack(const ATitle: String): TCDTrack;
+begin
+  Result:=AddTrack(ATitle,'',0);
+end;
+
+{ TCDDisks }
+
+function TCDDisks.GetD(AIndex : Integer): TCDDisk;
+begin
+  Result:=Items[AIndex] as TCDDisk;
+end;
+
+procedure TCDDisks.SetD(AIndex : Integer; const AValue: TCDDisk);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TCDDisks.AddDisk(ADiscID: String): TCDDisk;
+begin
+  Result:=Self.AddDisk();
+  Result.DiscID:=ADiscID;
+end;
+
+function TCDDisks.AddDisk: TCDDisk;
+begin
+  Result:=Add as TCDDisk;
+end;
+
+{ TCDDBParser }
+
+procedure TCDDBParser.SetDisks(const AValue: TCDDisks);
+begin
+  if FDisks=AValue then exit;
+  FDisks.Assign(AValue);
+end;
+
+procedure TCDDBParser.CheckDisk;
+begin
+  If (FDisk=Nil) then
+    Raise ECDDBParser.Create(SErrNoDisk)
+end;
+
+function TCDDBParser.CreateDisks: TCDDisks;
+begin
+  Result:=TCDDisks.Create(TCDDisk);
+end;
+
+function TCDDBParser.IsComment(const L: String): Boolean;
+begin
+  Result:=(Length(L)=0) or (L[1]='#');
+end;
+
+function TCDDBParser.GetTrack(const TrackNo: Integer): TCDTrack;
+begin
+  If (TrackNo<0) then
+    Raise ECDDBParser.CreateFmt(SErrInvalidTrackNo,[TrackNo]);
+  CheckDisk;
+  If (TrackNo>FDisk.Tracks.Count) then
+    Raise ECDDBParser.CreateFmt(SErrInvalidTrackNo,[TrackNo]);
+  If (TrackNo=FDisk.Tracks.Count) then
+    Result:=FDisk.Tracks.AddTrack('')
+  else
+    Result:=FDisk.Tracks[TrackNo]
+end;
+
+constructor TCDDBParser.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDisks:=CreateDisks;
+end;
+
+destructor TCDDBParser.Destroy;
+begin
+  FreeAndNil(FDisks);
+  inherited Destroy;
+end;
+
+Function TCDDBParser.StdReplacements(S : String) : String;
+
+begin
+  Result:=StringReplace(S,'\n',sLineBreak,[rfReplaceAll]);
+end;
+
+Function TCDDBParser.ParseExtraDiskData(AData : String) : Boolean;
+
+begin
+  FDisk.Extra:=FDisk.Extra+StdReplacements(AData);
+end;
+
+Function TCDDBParser.ParseExtraTrackData(ATrack : TCDTrack; AData : String) : Boolean;
+
+begin
+  ATrack.Extra:=ATrack.Extra+StdReplacements(AData);
+end;
+
+Procedure TCDDBParser.SplitTitle(Const ALine: String; Var AArtist, ATitle : String; PreferTitle : boolean);
+
+Var
+  P,L : Integer;
+
+begin
+  // Artist / Title
+  L:=Length(ALine);
+  P:=Pos('/',ALine);
+  If (P=0) and Not PreferTitle then
+    P:=L+1;
+  AArtist:=Trim(Copy(ALine,1,P-1));
+  ATitle:=Trim(Copy(ALine,P+1,L-P));
+end;
+
+Function TCDDBParser.ParseCDDBReadResponse(Response: TStrings; WithHeader : Boolean = True) : Integer;
+
+Var
+  I,P : Integer;
+  L,Args,A,T : String;
+  TrackID : Integer;
+  Track : TCDTrack;
+
+begin
+  Result:=-1;
+  FDisks.Clear;
+  If WithHeader and (Response.Count>0) then
+    begin
+    L:=Response[0];
+    If Not (CheckCDDBCmdResult(L) in [200,210]) then
+      Raise ECDDBParser.CreateFmt(SErrCDDBResponse,[L]);
+    end;
+  FDisk:=Nil;
+  Result:=0;
+  Try
+    Try
+      I:=Ord(WithHeader);
+      While (I<Response.Count) do
+        begin
+        L:=Response[i];
+        If Not IsComment(L) then
+          begin
+          P:=Pos('=',L);
+          Args:=Copy(L,P+1,Length(L)-P);
+          L:=Uppercase(Copy(L,1,P-1));
+          If (L='DISCID') then
+            FDisk:=FDisks.AddDisk(Args)
+          else
+            begin
+            CheckDisk;
+            If (L='DTITLE') then
+              begin
+              SplitTitle(Args,A,T,True);
+              FDisk.Title:=T;
+              FDisk.Performer:=A;
+              end
+            else if (L='EXTD') then
+              ParseExtraDiskData(Args)
+            else if (Copy(L,1,6)='TTITLE') then
+              begin
+              Delete(L,1,6);
+              TrackID:=StrToIntDef(L,-1);
+              Track:=GetTrack(TrackID);
+              SplitTitle(Args,A,T,True);
+              Track.Title:=T;
+              Track.Performer:=A;
+              end
+            else if (Copy(L,1,6)='EXTT') then
+              begin
+              Delete(L,1,6);
+              TrackID:=StrToIntDef(L,-1);
+              Track:=GetTrack(TrackID);
+              ParseExtraTrackData(Track,Args);
+              end
+            else if (Copy(L,1,9)='PLAYORDER') then
+              begin
+              FDisk.PlayOrder:=Trim(Args);
+              end;
+            end;
+          end;
+        Inc(I);
+        end;
+    except
+      On E : Exception do
+        begin
+        E.Message:=Format(SErrParsingLine,[I,E.MEssage]);
+        Raise;
+        end;
+    end;
+    Result:=FDisks.Count;
+  Finally
+    FDisk:=Nil;
+  end;
+end;
+
+Function TCDDBParser.ParseCDDBReadResponse(Response: TStream; WithHeader : Boolean = True) : Integer;
+
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.LoadFromStream(Response);
+    Result:=ParseCDDBReadResponse(L,WithHeader);
+  finally
+    L.Free;
+  end;
+end;
+
+function TCDDBParser.ParseCDDBQueryResponse(Response: TStrings;
+  Matches: TCDDBQueryMatches; WithHeader: Boolean): Integer;
+
+Var
+  I,CmdRes : Integer;
+  L : String;
+  D,C,T,P : String;
+
+begin
+  Matches.Clear;
+  Result:=-1;
+  If WithHeader and (Response.Count>0) then
+    begin
+    L:=Response[0];
+    CmdRes:=CheckCDDBCmdResult(L);
+    If (CmdRes=200) then
+      begin
+      SplitQueryResponse(L,C,D,T,P);
+      Matches.AddMatch(D,C,T,P);
+      Result:=1;
+      Exit;
+      end
+    else if (CmdRes<>210) then
+      Raise ECDDBParser.CreateFmt(SerrCDDBResponse,[L]);
+    end;
+  For I:=Ord(WithHeader) to Response.Count-1 do
+    begin
+    SplitQueryResponse(Response[i],C,D,T,P);
+    Matches.AddMatch(D,C,T,P);
+    end;
+  Result:=Matches.Count;
+end;
+
+function TCDDBParser.ParseCDDBQueryResponse(Response: TStream;
+  Matches: TCDDBQueryMatches; WithHeader: Boolean): Integer;
+
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.LoadFromStream(Response);
+    Result:=ParseCDDBQueryResponse(L,Matches,WithHeader);
+  finally
+    L.Free;
+  end;
+end;
+
+Function TCDDBParser.CheckCDDBCmdResult(Var S : String) : Integer;
+
+Var
+  P : integer;
+
+begin
+  P:=Pos(' ',S);
+  If (P=0) then
+    P:=Length(S)+1;
+  Result:=StrToIntDef(Copy(S,1,P-1),0);
+  Delete(S,1,P);
+end;
+
+Procedure TCDDBParser.SplitQueryResponse(AResponse :String; Var ACategory, ADiscID, ATitle, APerformer : String);
+
+Var
+  P : Integer;
+
+begin
+  P:=Pos(' ',AResponse);
+  ACategory:=Copy(AResponse,1,P-1);
+  Delete(AResponse,1,P);
+  P:=Pos(' ',AResponse);
+  ADiscId:=Copy(AResponse,1,P-1);
+  Delete(AResponse,1,P);
+  SplitTitle(AResponse,APerformer,ATitle,True);
+end;
+
+{ TCDDBQueryMatches }
+
+function TCDDBQueryMatches.GetM(AIndex : Integer): TCDDBQueryMatch;
+begin
+  Result:=TCDDBQueryMatch(Items[AIndex]);
+end;
+
+procedure TCDDBQueryMatches.SetM(AIndex : Integer; const AValue: TCDDBQueryMatch
+  );
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TCDDBQueryMatches.AddMatch(const ADiscID: Integer; const ACategory,
+  ATitle, APerformer: String): TCDDBQueryMatch;
+begin
+  Result:=AddMatch();
+  Result.DiscID:=ADiscID;
+  Result.Category:=ACategory;
+  Result.Title:=ATitle;
+  Result.Performer:=APerformer;
+end;
+
+function TCDDBQueryMatches.AddMatch(const ADiscID, ACategory, ATitle, APerformer : String): TCDDBQueryMatch;
+
+begin
+  Result:=AddMatch(StrToDiscID(ADiscID),ACategory,ATitle,APerformer);
+end;
+
+function TCDDBQueryMatches.AddMatch: TCDDBQueryMatch;
+begin
+  Result:=Add as TCDDBQueryMatch;
+end;
+
+{ TCDDBQueryMatch }
+
+procedure TCDDBQueryMatch.Assign(Source: TPersistent);
+
+Var
+  M : TCDDBQueryMatch;
+
+begin
+  if Source is TCDDBQueryMatch then
+    begin
+    M:=Source as TCDDBQueryMatch;
+    FDiscID:=M.FDiscID;
+    FCategory:=M.FCategory;
+    FPerformer:=M.FPerformer;
+    FTitle:=M.FTitle;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+end.
+

+ 62 - 58
packages/fcl-base/Makefile

@@ -1,5 +1,5 @@
 #
-# Don't edit, this file is generated by FPCMake Version 2.0.0 [2008/06/15]
+# Don't edit, this file is generated by FPCMake Version 2.0.0 [2008/06/18]
 #
 default: all
 MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian m68k-linux m68k-freebsd m68k-netbsd m68k-amiga m68k-atari m68k-openbsd m68k-palmos m68k-embedded powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macos powerpc-darwin powerpc-morphos powerpc-embedded sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-darwin x86_64-win64 x86_64-embedded arm-linux arm-palmos arm-darwin arm-wince arm-gba arm-nds arm-embedded arm-symbian powerpc64-linux powerpc64-darwin powerpc64-embedded avr-embedded armeb-linux armeb-embedded
@@ -267,178 +267,182 @@ ifeq ($(OS_TARGET),win64)
 INSTALL_DATADIR=${INSTALL_UNITDIR}
 endif
 ifeq ($(FULL_TARGET),i386-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),i386-go32v2)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-win32)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  fileinfo syncobjs daemonapp ServiceManager fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  fileinfo syncobjs daemonapp ServiceManager fptimer
 endif
 ifeq ($(FULL_TARGET),i386-os2)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-freebsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),i386-beos)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs
 endif
 ifeq ($(FULL_TARGET),i386-haiku)
+<<<<<<< .working
 override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+=======
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs
+>>>>>>> .merge-right.r12011
 endif
 ifeq ($(FULL_TARGET),i386-netbsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),i386-solaris)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),i386-qnx)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-netware)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs
 endif
 ifeq ($(FULL_TARGET),i386-openbsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),i386-wdosx)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-darwin)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),i386-emx)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-watcom)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-netwlibc)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs
 endif
 ifeq ($(FULL_TARGET),i386-wince)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  fileinfo syncobjs fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  fileinfo syncobjs fptimer
 endif
 ifeq ($(FULL_TARGET),i386-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-symbian)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),m68k-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),m68k-freebsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),m68k-netbsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),m68k-amiga)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),m68k-atari)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),m68k-openbsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),m68k-palmos)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),m68k-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),powerpc-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),powerpc-netbsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),powerpc-amiga)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),powerpc-macos)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),powerpc-darwin)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),powerpc-morphos)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),powerpc-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),sparc-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),sparc-netbsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),sparc-solaris)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),sparc-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),x86_64-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),x86_64-freebsd)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),x86_64-darwin)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),x86_64-win64)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  fileinfo syncobjs daemonapp ServiceManager fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  fileinfo syncobjs daemonapp ServiceManager fptimer
 endif
 ifeq ($(FULL_TARGET),x86_64-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),arm-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),arm-palmos)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),arm-darwin)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),arm-wince)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  fileinfo syncobjs fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  fileinfo syncobjs fptimer
 endif
 ifeq ($(FULL_TARGET),arm-gba)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),arm-nds)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),arm-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),arm-symbian)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),powerpc64-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),powerpc64-darwin)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),powerpc64-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),avr-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),armeb-linux)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils  syncobjs daemonapp fptimer
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars  syncobjs daemonapp fptimer
 endif
 ifeq ($(FULL_TARGET),armeb-embedded)
-override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+override TARGET_UNITS+=contnrs inifiles rtfpars idea base64 gettext iostream cachecls avl_tree uriparser eventlog custapp wformat whtml wtex rttiutils bufstream streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils fpexprpars
 endif
 ifeq ($(FULL_TARGET),i386-linux)
 override TARGET_RSTS+=cachecls custapp cgiapp eventlog registry streamcoll inicol

+ 2 - 1
packages/fcl-base/Makefile.fpc

@@ -14,7 +14,8 @@ packages_win64=winunits-base winunits-jedi
 units=contnrs inifiles rtfpars idea base64 gettext \
       iostream cachecls avl_tree uriparser \
       eventlog custapp wformat whtml wtex rttiutils bufstream \
-      streamex blowfish streamio inicol pooledmm libtar streamcoll maskutils
+      streamex blowfish streamio inicol pooledmm libtar streamcoll \
+      maskutils fpexprpars
 units_beos=syncobjs 
 units_freebsd=syncobjs daemonapp fptimer
 units_darwin=syncobjs daemonapp fptimer 

+ 140 - 0
packages/fcl-base/examples/fpexprpars.txt

@@ -0,0 +1,140 @@
+
+The fpexprpars unit contains an expression parser.
+The parser compiles the expression into a node tree, which is 
+type checked after it was compiled.
+
+The expression parser handles the following types:
+  String 
+  Integer (64-bit)
+  Float (TExprFloat, normally Double) 
+  TDateTime
+  Boolean 
+
+The following operations are allowed:
+  + - / * 
+  not and or xor
+  ( ) 
+The binary operations can also be done on integer values, in which
+case they act on the bits of the integer.
+
+In the case of strings addition results in concatenation of the strings.
+
+Operator precedence is observed. In case of equal precedence, evaluation
+order is left-to-right.
+
+Normally, both operands of binary operations must have the same type.
+There are 2 exceptions: The engine will convert integers to float or
+TDateTime if it detects that one of the nodes is a float or datetime.
+
+The engine can be extended with variables and functions. There are over
+60 built-in functions, which can be enabled by setting the Builtins property
+of the expression parser to a set of the following values:
+
+ bcStrings: Various string routines
+    length copy delete pos lowercase uppercase stringreplace comparetext
+
+ bcDateTime: Various datetime routines
+    date time now dayofweek extractyear extractmonth extractday extracthour
+    extractmin extractsec extractmsec encodedate encodetime encodedatetime
+    shortdayname shortmonthname longdayname longmonthname formatdatetime   
+
+ bcMath: Various mathematical routines
+    pi cos sin arctan abs sqr sqrt exp ln log frac int round trunc
+
+ bcBoolean: Various boolean routines
+    shl shr IFS IFF IFD IFI  
+
+ bcConversion : Conversion routines
+   inttostr strtoint strtointdef floattostr strtofloat strtofloatdef
+   booltostr strtobool strtobooldef datetostr timetostr strtodate strtodatedef
+   strtotime strtotimedef strtodatetime strtodatetimedef 
+
+Additional functions/variables can be added to the Identifiers collection:
+
+  FP : TFPexpressionParser;
+
+The following will define a TODAY variable which has value equal to the date
+at the moment is is defined:
+
+  FP.Identifiers.AddDateTimeVariable('TODAY',Date);
+  
+The following will define a function echodate:
+
+Procedure EchoDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=Args[0].resDateTime;
+end;
+
+  FP.Identifiers.AddFunction('EchoDate','D','D',@EchoDate);
+
+The arguments are:
+  Name : Name of the function
+  Result type : Character with result type:
+    I : integer
+    S : String
+    F : FLoat
+    D : TDateTime
+    B : Boolean
+  Argument types : A string with each character the type of argument at that position.
+  Callback : executed when the function is called. This can be a procedural
+             variable or an event (procedure of object).
+
+Result and arguments are type-checked.
+  
+The engine knows 2 built-in functions which are handled specially:
+
+  IF(Expr,Res1,Res1)
+
+Will return Res1 if expr evaluates to True, or Res2 if expr evaluates to False.
+The types of Res1 and Res2 must be the same, and expr must be a boolean
+expression.
+
+  CASE(Tag,Def,Label1,Value1,Label2,Value2,...)
+
+Case will examine the value of Tag and compare it with Label1, Label2 etc.
+till a match is found. It will return Value1, Value2 etc. depending on the
+match. If no match is found, Def will be returned. From this it follows that
+1) The number of arguments is always even and is at least 4.
+2) The types of Tag, label1, label2 must be the same;
+3) The types of Def, Value1, Value2 must be the same;
+
+As soon as the expression is set, it is compiled and checked. Thus
+   FP.Expression:='1*2';
+will work.
+
+On the other hand
+   FP.Expression:=' 1 * ''a string''';
+will result in an exception because 1 and ''a string'' do not have the same
+type.
+
+Getting the result is quite simple
+
+  FP.Expression:='1*2';
+  Writeln(FP.AsInteger);
+
+This will raise an exception if the type of the result is not integer.
+
+In case the expression result type is unknown, it can be examined using 
+the ResultType function, as in :
+
+  FP.Expression:='Some user-provided expression';
+  Case FP.ResultType of
+    rtString  : Writeln(FP.Evaluate.ResString);
+    rtInteger : Writeln(FP.Evaluate.ResInteger);
+    rtFloat   : Writeln(FP.Evaluate.ResFloat);
+    rtBoolean : Writeln(FP.Evaluate.ResBoolean);
+    rtDateTime : Writeln(FormatDateTime('cccc',FP.Evaluate.ResDateTime));
+  end;   
+
+Which is equivalent to
+
+  FP.Expression:='Some user-provided expression';
+  Case FP.ResultType of
+    rtString  : Writeln(FP.AsString);
+    rtInteger : Writeln(FP.AsInteger);
+    rtFloat   : Writeln(FP.AsFloat);
+    rtBoolean : Writeln(FP.AsBoolean);
+    rtDateTime : Writeln(FormatDateTime('cccc',FP.AsDateTime));
+  end;
+

+ 5999 - 0
packages/fcl-base/examples/testexprpars.pp

@@ -0,0 +1,5999 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2008 Michael Van Canneyt.
+    
+    File which provides examples and all testcases for the expression parser.
+    It needs fcl-fpcunit to work.
+    
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit testexprpars;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry,fpexprpars;
+
+type
+
+  { TTestExpressionScanner }
+
+  TTestExpressionScanner = class(TTestCase)
+  Private
+    FP : TFPExpressionScanner;
+    FInvalidString : String;
+    procedure DoInvalidNumber(AString: String);
+    procedure TestInvalidNumber;
+  protected
+    procedure SetUp; override; 
+    procedure TearDown; override;
+    Procedure AssertEquals(Msg : string; AExpected, AActual : TTokenType); overload;
+    Procedure TestString(Const AString : String; AToken : TTokenType);
+  published
+    procedure TestCreate;
+    procedure TestSetSource;
+    Procedure TestWhiteSpace;
+    Procedure TestTokens;
+    Procedure TestNumber;
+    Procedure TestInvalidCharacter;
+    Procedure TestUnterminatedString;
+    Procedure TestQuotesInString;
+  end;
+
+  { TMyFPExpressionParser }
+
+  TMyFPExpressionParser = Class(TFPExpressionParser)
+  Public
+    Procedure BuildHashList;
+    Property ExprNode;
+    Property Scanner;
+    Property Dirty;
+  end;
+
+  { TTestBaseParser }
+
+  TTestBaseParser = class(TTestCase)
+  private
+    procedure DoCheck;
+  Protected
+    FDestroyCalled : Integer;
+    FCheckNode : TFPExprNode;
+    procedure AssertNodeType(Msg: String; AClass: TClass; ANode: TFPExprNode); overload;
+    procedure AssertEquals(Msg: String; AResultType : TResultType; ANode: TFPExprNode); overload;
+    procedure AssertEquals(Msg: String; AExpected,AActual : TResultType); overload;
+    Function CreateBoolNode(ABoolean: Boolean) : TFPExprNode;
+    Function CreateIntNode(AInteger: Integer) : TFPExprNode;
+    Function CreateFloatNode(AFloat : TExprFloat) : TFPExprNode;
+    Function CreateStringNode(Astring : String) : TFPExprNode;
+    Function CreateDateTimeNode(ADateTime : TDateTime) : TFPExprNode;
+    Procedure AssertNodeOK(FN : TFPExprNode);
+    Procedure AssertNodeNotOK(Const Msg : String; FN : TFPExprNode);
+    Procedure Setup; override;
+  end;
+
+  { TMyDestroyNode }
+
+  TMyDestroyNode = Class(TFPConstExpression)
+    FTest : TTestBaseParser;
+  Public
+    Constructor CreateTest(ATest : TTestBaseParser);
+    Destructor Destroy; override;
+  end;
+
+  { TTestDestroyNode }
+
+  TTestDestroyNode =  Class(TTestBaseParser)
+  Published
+    Procedure TestDestroy;
+  end;
+
+  { TTestConstExprNode }
+
+  TTestConstExprNode = Class(TTestBaseParser)
+  private
+    FN : TFPConstExpression;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateFloat;
+    procedure TestCreateBoolean;
+    procedure TestCreateDateTime;
+    procedure TestCreateString;
+  end;
+
+  { TTestNegateExprNode }
+
+  TTestNegateExprNode = Class(TTestBaseParser)
+  Private
+    FN : TFPNegateOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateFloat;
+    procedure TestCreateOther1;
+    procedure TestCreateOther2;
+    Procedure TestDestroy;
+  end;
+
+  { TTestBinaryAndNode }
+
+  TTestBinaryAndNode = Class(TTestBaseParser)
+  Private
+    FN : TFPBinaryAndOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateBoolean;
+    procedure TestCreateBooleanInteger;
+    procedure TestCreateString;
+    procedure TestCreateFloat;
+    procedure TestCreateDateTime;
+    Procedure TestDestroy;
+  end;
+
+  { TTestNotNode }
+
+  TTestNotNode = Class(TTestBaseParser)
+  Private
+    FN : TFPNotNode;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateBoolean;
+    procedure TestCreateString;
+    procedure TestCreateFloat;
+    procedure TestCreateDateTime;
+    Procedure TestDestroy;
+  end;
+
+  { TTestBinaryOrNode }
+
+  TTestBinaryOrNode = Class(TTestBaseParser)
+  Private
+    FN : TFPBinaryOrOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateBoolean;
+    procedure TestCreateBooleanInteger;
+    procedure TestCreateString;
+    procedure TestCreateFloat;
+    procedure TestCreateDateTime;
+    Procedure TestDestroy;
+  end;
+
+  { TTestBinaryXOrNode }
+
+  TTestBinaryXOrNode = Class(TTestBaseParser)
+  Private
+    FN : TFPBinaryXOrOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateBoolean;
+    procedure TestCreateBooleanInteger;
+    procedure TestCreateString;
+    procedure TestCreateFloat;
+    procedure TestCreateDateTime;
+    Procedure TestDestroy;
+  end;
+
+  { TTestIfOperation }
+
+  TTestIfOperation = Class(TTestBaseParser)
+  Private
+    FN : TIfOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    procedure TestCreateBoolean;
+    procedure TestCreateBoolean2;
+    procedure TestCreateString;
+    procedure TestCreateFloat;
+    procedure TestCreateDateTime;
+    procedure TestCreateBooleanInteger;
+    procedure TestCreateBooleanInteger2;
+    procedure TestCreateBooleanString;
+    procedure TestCreateBooleanString2;
+    procedure TestCreateBooleanDateTime;
+    procedure TestCreateBooleanDateTime2;
+    Procedure TestDestroy;
+  end;
+
+  { TTestCaseOperation }
+
+  TTestCaseOperation = Class(TTestBaseParser)
+  Private
+    FN : TCaseOperation;
+  Protected
+    Function CreateArgs(Args : Array of Const) : TExprArgumentArray;
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateOne;
+    procedure TestCreateTwo;
+    procedure TestCreateThree;
+    procedure TestCreateOdd;
+    procedure TestCreateNoExpression;
+    procedure TestCreateWrongLabel;
+    procedure TestCreateWrongValue;
+    procedure TestIntegerTag;
+    procedure TestIntegerTagDefault;
+    procedure TestStringTag;
+    procedure TestStringTagDefault;
+    procedure TestFloatTag;
+    procedure TestFloatTagDefault;
+    procedure TestBooleanTag;
+    procedure TestBooleanTagDefault;
+    procedure TestDateTimeTag;
+    procedure TestDateTimeTagDefault;
+    procedure TestIntegerValue;
+    procedure TestIntegerValueDefault;
+    procedure TestStringValue;
+    procedure TestStringValueDefault;
+    procedure TestFloatValue;
+    procedure TestFloatValueDefault;
+    procedure TestBooleanValue;
+    procedure TestBooleanValueDefault;
+    procedure TestDateTimeValue;
+    procedure TestDateTimeValueDefault;
+    Procedure TestDestroy;
+  end;
+
+  { TTestBooleanNode }
+
+  TTestBooleanNode = Class(TTestBaseParser)
+  Protected
+    Procedure TestNode(B : TFPBooleanResultOperation; AResult : Boolean);
+  end;
+
+  { TTestEqualNode }
+
+  TTestEqualNode = Class(TTestBooleanNode)
+  Private
+    FN : TFPBooleanResultOperation;
+  Protected
+    Procedure TearDown; override;
+    Class Function NodeClass : TFPBooleanResultOperationClass; virtual;
+    Class Function ExpectedResult : Boolean; virtual;
+    Class Function OperatorString : String; virtual;
+  Published
+    Procedure TestCreateIntegerEqual;
+    procedure TestCreateIntegerUnEqual;
+    Procedure TestCreateFloatEqual;
+    procedure TestCreateFloatUnEqual;
+    Procedure TestCreateStringEqual;
+    procedure TestCreateStringUnEqual;
+    Procedure TestCreateBooleanEqual;
+    procedure TestCreateBooleanUnEqual;
+    Procedure TestCreateDateTimeEqual;
+    procedure TestCreateDateTimeUnEqual;
+    Procedure TestDestroy;
+    Procedure TestWrongTypes1;
+    procedure TestWrongTypes2;
+    procedure TestWrongTypes3;
+    procedure TestWrongTypes4;
+    procedure TestWrongTypes5;
+    Procedure TestAsString;
+  end;
+
+  { TTestUnEqualNode }
+
+  TTestUnEqualNode = Class(TTestEqualNode)
+  Protected
+    Class Function NodeClass : TFPBooleanResultOperationClass; override;
+    Class Function ExpectedResult : Boolean; override;
+    Class Function OperatorString : String; override;
+  end;
+
+  { TTestLessThanNode }
+
+  TTestLessThanNode = Class(TTestBooleanNode)
+  Private
+    FN : TFPBooleanResultOperation;
+  Protected
+    Class Function NodeClass : TFPBooleanResultOperationClass; virtual;
+    Class Function Larger : Boolean; virtual;
+    Class Function AllowEqual : Boolean; virtual;
+    Class Function OperatorString : String; virtual;
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateIntegerEqual;
+    procedure TestCreateIntegerSmaller;
+    procedure TestCreateIntegerLarger;
+    Procedure TestCreateFloatEqual;
+    procedure TestCreateFloatSmaller;
+    procedure TestCreateFloatLarger;
+    Procedure TestCreateDateTimeEqual;
+    procedure TestCreateDateTimeSmaller;
+    procedure TestCreateDateTimeLarger;
+    Procedure TestCreateStringEqual;
+    procedure TestCreateStringSmaller;
+    procedure TestCreateStringLarger;
+    Procedure TestWrongTypes1;
+    procedure TestWrongTypes2;
+    procedure TestWrongTypes3;
+    procedure TestWrongTypes4;
+    procedure TestWrongTypes5;
+    Procedure TestNoBoolean1;
+    Procedure TestNoBoolean2;
+    Procedure TestNoBoolean3;
+    Procedure TestAsString;
+  end;
+
+  { TTestLessThanEqualNode }
+
+  TTestLessThanEqualNode = Class(TTestLessThanNode)
+  protected
+    Class Function NodeClass : TFPBooleanResultOperationClass; override;
+    Class Function AllowEqual : Boolean; override;
+    Class Function OperatorString : String; override;
+  end;
+
+  { TTestLargerThanNode }
+
+  TTestLargerThanNode = Class(TTestLessThanNode)
+  protected
+    Class Function NodeClass : TFPBooleanResultOperationClass; override;
+    Class Function Larger : Boolean; override;
+    Class Function OperatorString : String; override;
+  end;
+  { TTestLargerThanEqualNode }
+
+  TTestLargerThanEqualNode = Class(TTestLargerThanNode)
+  protected
+    Class Function NodeClass : TFPBooleanResultOperationClass; override;
+    Class Function AllowEqual : Boolean; override;
+    Class Function OperatorString : String; override;
+  end;
+
+  { TTestAddNode }
+
+  TTestAddNode = Class(TTestBaseParser)
+  Private
+    FN : TFPAddOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestCreateDateTime;
+    Procedure TestCreateString;
+    Procedure TestCreateBoolean;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestSubtractNode }
+
+  TTestSubtractNode = Class(TTestBaseParser)
+  Private
+    FN : TFPSubtractOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestCreateDateTime;
+    Procedure TestCreateString;
+    Procedure TestCreateBoolean;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestMultiplyNode }
+
+  TTestMultiplyNode = Class(TTestBaseParser)
+  Private
+    FN : TFPMultiplyOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestCreateDateTime;
+    Procedure TestCreateString;
+    Procedure TestCreateBoolean;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestDivideNode }
+
+  TTestDivideNode = Class(TTestBaseParser)
+  Private
+    FN : TFPDivideOperation;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestCreateDateTime;
+    Procedure TestCreateString;
+    Procedure TestCreateBoolean;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestIntToFloatNode }
+
+  TTestIntToFloatNode = Class(TTestBaseParser)
+  Private
+    FN : TIntToFloatNode;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestIntToDateTimeNode }
+
+  TTestIntToDateTimeNode = Class(TTestBaseParser)
+  Private
+    FN : TIntToDateTimeNode;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestFloatToDateTimeNode }
+
+  TTestFloatToDateTimeNode = Class(TTestBaseParser)
+  Private
+    FN : TFloatToDateTimeNode;
+  Protected
+    Procedure TearDown; override;
+  Published
+    Procedure TestCreateInteger;
+    Procedure TestCreateFloat;
+    Procedure TestDestroy;
+    Procedure TestAsString;
+  end;
+
+  { TTestExpressionParser }
+  TTestExpressionParser = class(TTestBaseParser)
+  Private
+    FP : TMyFPExpressionParser;
+    FTestExpr : String;
+    procedure DoAddInteger(var Result: TFPExpressionResult;
+      const Args: TExprParameterArray);
+    procedure DoDeleteString(var Result: TFPExpressionResult;
+      const Args: TExprParameterArray);
+    procedure DoEchoBoolean(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+    procedure DoEchoDate(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+    procedure DoEchoFloat(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+    procedure DoEchoInteger(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+    procedure DoEchoString(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+    procedure DoGetDate(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+    procedure DoParse;
+    procedure TestParser(AExpr: string);
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    Procedure AssertLeftRight(N : TFPExprNode; LeftClass,RightClass : TClass);
+    Procedure AssertOperand(N : TFPExprNode; OperandClass : TClass);
+    Procedure AssertResultType(RT : TResultType);
+    Procedure AssertResult(F : TExprFloat);
+    Procedure AssertResult(I : Int64);
+    Procedure AssertResult(S : String);
+    Procedure AssertResult(B : Boolean);
+    Procedure AssertDateTimeResult(D : TDateTime);
+  end;
+
+  { TTestParserExpressions }
+
+  TTestParserExpressions = Class(TTestExpressionParser)
+  private
+  Published
+    Procedure TestCreate;
+    Procedure TestSimpleNodeFloat;
+    procedure TestSimpleNodeInteger;
+    procedure TestSimpleNodeBooleanTrue;
+    procedure TestSimpleNodeBooleanFalse;
+    procedure TestSimpleNodeString;
+    procedure TestSimpleNegativeInteger;
+    procedure TestSimpleNegativeFloat;
+    procedure TestSimpleAddInteger;
+    procedure TestSimpleAddFloat;
+    procedure TestSimpleAddIntegerFloat;
+    procedure TestSimpleAddFloatInteger;
+    procedure TestSimpleAddString;
+    procedure TestSimpleSubtractInteger;
+    procedure TestSimpleSubtractFloat;
+    procedure TestSimpleSubtractIntegerFloat;
+    procedure TestSimpleSubtractFloatInteger;
+    procedure TestSimpleMultiplyFloat;
+    procedure TestSimpleMultiplyInteger;
+    procedure TestSimpleDivideFloat;
+    procedure TestSimpleDivideInteger;
+    procedure TestSimpleBooleanAnd;
+    procedure TestSimpleIntegerAnd;
+    procedure TestSimpleBooleanOr;
+    procedure TestSimpleIntegerOr;
+    procedure TestSimpleBooleanNot;
+    procedure TestSimpleIntegerNot;
+    procedure TestSimpleAddSeries;
+    procedure TestSimpleMultiplySeries;
+    procedure TestSimpleAddMultiplySeries;
+    procedure TestSimpleAddAndSeries;
+    procedure TestSimpleAddOrSeries;
+    procedure TestSimpleOrNotSeries;
+    procedure TestSimpleAndNotSeries;
+    procedure TestDoubleAddMultiplySeries;
+    procedure TestDoubleSubtractMultiplySeries;
+    procedure TestSimpleIfInteger;
+    procedure TestSimpleIfString;
+    procedure TestSimpleIfFloat;
+    procedure TestSimpleIfBoolean;
+    procedure TestSimpleIfDateTime;
+    procedure TestSimpleIfOperation;
+    procedure TestSimpleBrackets;
+    procedure TestSimpleBrackets2;
+    procedure TestSimpleBracketsLeft;
+    procedure TestSimpleBracketsRight;
+    procedure TestSimpleBracketsDouble;
+  end;
+
+  TTestParserBooleanOperations = Class(TTestExpressionParser)
+  Published
+    Procedure TestEqualInteger;
+    procedure TestUnEqualInteger;
+    procedure TestEqualFloat;
+    procedure TestEqualFloat2;
+    procedure TestUnEqualFloat;
+    procedure TestEqualString;
+    procedure TestEqualString2;
+    procedure TestUnEqualString;
+    procedure TestUnEqualString2;
+    Procedure TestEqualBoolean;
+    procedure TestUnEqualBoolean;
+    procedure TestLessThanInteger;
+    procedure TestLessThanInteger2;
+    procedure TestLessThanEqualInteger;
+    procedure TestLessThanEqualInteger2;
+    procedure TestLessThanFloat;
+    procedure TestLessThanFloat2;
+    procedure TestLessThanEqualFloat;
+    procedure TestLessThanEqualFloat2;
+    procedure TestLessThanString;
+    procedure TestLessThanString2;
+    procedure TestLessThanEqualString;
+    procedure TestLessThanEqualString2;
+    procedure TestGreaterThanInteger;
+    procedure TestGreaterThanInteger2;
+    procedure TestGreaterThanEqualInteger;
+    procedure TestGreaterThanEqualInteger2;
+    procedure TestGreaterThanFloat;
+    procedure TestGreaterThanFloat2;
+    procedure TestGreaterThanEqualFloat;
+    procedure TestGreaterThanEqualFloat2;
+    procedure TestGreaterThanString;
+    procedure TestGreaterThanString2;
+    procedure TestGreaterThanEqualString;
+    procedure TestGreaterThanEqualString2;
+    procedure EqualAndSeries;
+    procedure EqualAndSeries2;
+    procedure EqualOrSeries;
+    procedure EqualOrSeries2;
+    procedure UnEqualAndSeries;
+    procedure UnEqualAndSeries2;
+    procedure UnEqualOrSeries;
+    procedure UnEqualOrSeries2;
+    procedure LessThanAndSeries;
+    procedure LessThanAndSeries2;
+    procedure LessThanOrSeries;
+    procedure LessThanOrSeries2;
+    procedure GreaterThanAndSeries;
+    procedure GreaterThanAndSeries2;
+    procedure GreaterThanOrSeries;
+    procedure GreaterThanOrSeries2;
+    procedure LessThanEqualAndSeries;
+    procedure LessThanEqualAndSeries2;
+    procedure LessThanEqualOrSeries;
+    procedure LessThanEqualOrSeries2;
+    procedure GreaterThanEqualAndSeries;
+    procedure GreaterThanEqualAndSeries2;
+    procedure GreaterThanEqualOrSeries;
+    procedure GreaterThanEqualOrSeries2;
+  end;
+
+  { TTestParserOperands }
+
+  TTestParserOperands = Class(TTestExpressionParser)
+  private
+  Published
+    Procedure MissingOperand1;
+    procedure MissingOperand2;
+    procedure MissingOperand3;
+    procedure MissingOperand4;
+    procedure MissingOperand5;
+    procedure MissingOperand6;
+    procedure MissingOperand7;
+    procedure MissingOperand8;
+    procedure MissingOperand9;
+    procedure MissingOperand10;
+    procedure MissingOperand11;
+    procedure MissingOperand12;
+    procedure MissingOperand13;
+    procedure MissingOperand14;
+    procedure MissingOperand15;
+    procedure MissingOperand16;
+    procedure MissingOperand17;
+    procedure MissingOperand18;
+    procedure MissingOperand19;
+    procedure MissingOperand20;
+    procedure MissingOperand21;
+    procedure MissingBracket1;
+    procedure MissingBracket2;
+    procedure MissingBracket3;
+    procedure MissingBracket4;
+    procedure MissingBracket5;
+    procedure MissingBracket6;
+    procedure MissingBracket7;
+    procedure MissingArgument1;
+    procedure MissingArgument2;
+    procedure MissingArgument3;
+    procedure MissingArgument4;
+    procedure MissingArgument5;
+    procedure MissingArgument6;
+    procedure MissingArgument7;
+  end;
+
+  { TTestParserTypeMatch }
+
+  TTestParserTypeMatch = Class(TTestExpressionParser)
+  Private
+    Procedure AccessString;
+    Procedure AccessInteger;
+    Procedure AccessFloat;
+    Procedure AccessDateTime;
+    Procedure AccessBoolean;
+  Published
+    Procedure TestTypeMismatch1;
+    procedure TestTypeMismatch2;
+    procedure TestTypeMismatch3;
+    procedure TestTypeMismatch4;
+    procedure TestTypeMismatch5;
+    procedure TestTypeMismatch6;
+    procedure TestTypeMismatch7;
+    procedure TestTypeMismatch8;
+    procedure TestTypeMismatch9;
+    procedure TestTypeMismatch10;
+    procedure TestTypeMismatch11;
+    procedure TestTypeMismatch12;
+    procedure TestTypeMismatch13;
+    procedure TestTypeMismatch14;
+    procedure TestTypeMismatch15;
+    procedure TestTypeMismatch16;
+    procedure TestTypeMismatch17;
+    procedure TestTypeMismatch18;
+    procedure TestTypeMismatch19;
+    procedure TestTypeMismatch20;
+    procedure TestTypeMismatch21;
+    procedure TestTypeMismatch22;
+    procedure TestTypeMismatch23;
+    procedure TestTypeMismatch24;
+  end;
+
+  { TTestParserVariables }
+
+  TTestParserVariables = Class(TTestExpressionParser)
+  private
+    FAsWrongType : TResultType;
+    procedure TestAccess(Skip: TResultType);
+  Protected
+    procedure AddVariabletwice;
+    procedure UnknownVariable;
+    Procedure ReadWrongType;
+    procedure WriteWrongType;
+    Procedure DoDummy(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+  Published
+    Procedure TestVariableAssign;
+    Procedure TestVariableAssignAgain;
+    Procedure TestVariable1;
+    procedure TestVariable2;
+    procedure TestVariable3;
+    procedure TestVariable4;
+    procedure TestVariable5;
+    procedure TestVariable6;
+    procedure TestVariable7;
+    procedure TestVariable8;
+    procedure TestVariable9;
+    procedure TestVariable10;
+    procedure TestVariable11;
+    procedure TestVariable12;
+    procedure TestVariable13;
+    procedure TestVariable14;
+    procedure TestVariable15;
+    procedure TestVariable16;
+    procedure TestVariable17;
+    procedure TestVariable18;
+    procedure TestVariable19;
+    procedure TestVariable20;
+    procedure TestVariable21;
+    procedure TestVariable22;
+    procedure TestVariable23;
+    procedure TestVariable24;
+    procedure TestVariable25;
+    procedure TestVariable26;
+    procedure TestVariable27;
+    procedure TestVariable28;
+    procedure TestVariable29;
+    procedure TestVariable30;
+  end;
+
+  { TTestParserFunctions }
+
+  TTestParserFunctions = Class(TTestExpressionParser)
+  private
+    FAccessAs : TResultType;
+    Procedure TryRead;
+    procedure TryWrite;
+  Published
+    Procedure TestFunction1;
+    procedure TestFunction2;
+    procedure TestFunction3;
+    procedure TestFunction4;
+    procedure TestFunction5;
+    procedure TestFunction6;
+    procedure TestFunction7;
+    procedure TestFunction8;
+    procedure TestFunction9;
+    procedure TestFunction10;
+    procedure TestFunction11;
+    procedure TestFunction12;
+    procedure TestFunction13;
+    procedure TestFunction14;
+    procedure TestFunction15;
+    procedure TestFunction16;
+    procedure TestFunction17;
+    procedure TestFunction18;
+    procedure TestFunction19;
+    procedure TestFunction20;
+    procedure TestFunction21;
+    procedure TestFunction22;
+    procedure TestFunction23;
+    procedure TestFunction24;
+    procedure TestFunction25;
+    procedure TestFunction26;
+    procedure TestFunction27;
+    procedure TestFunction28;
+    procedure TestFunction29;
+  end;
+
+  { TTestBuiltinsManager }
+
+  TTestBuiltinsManager = Class(TTestExpressionParser)
+  private
+    FM : TExprBuiltInManager;
+  Protected
+    procedure Setup; override;
+    procedure Teardown; override;
+  Published
+    procedure TestCreate;
+    procedure TestVariable1;
+    procedure TestVariable2;
+    procedure TestVariable3;
+    procedure TestVariable4;
+    procedure TestVariable5;
+    procedure TestVariable6;
+    procedure TestFunction1;
+    procedure TestFunction2;
+  end;
+
+  TTestBuiltins = Class(TTestExpressionParser)
+  private
+    FM : TExprBuiltInManager;
+    FExpr : String;
+  Protected
+    procedure Setup; override;
+    procedure Teardown; override;
+    Procedure SetExpression(Const AExpression : String);
+    Procedure AssertVariable(Const ADefinition : String; AResultType : TResultType);
+    Procedure AssertFunction(Const ADefinition,AResultType,ArgumentTypes : String; ACategory : TBuiltinCategory);
+    procedure AssertExpression(Const AExpression : String; AResult : Int64);
+    procedure AssertExpression(Const AExpression : String; Const AResult : String);
+    procedure AssertExpression(Const AExpression : String; Const AResult : TExprFloat);
+    procedure AssertExpression(Const AExpression : String; Const AResult : Boolean);
+    procedure AssertDateTimeExpression(Const AExpression : String; Const AResult : TDateTime);
+  Published
+    procedure TestRegister;
+    Procedure TestVariablepi;
+    Procedure TestFunctioncos;
+    Procedure TestFunctionsin;
+    Procedure TestFunctionarctan;
+    Procedure TestFunctionabs;
+    Procedure TestFunctionsqr;
+    Procedure TestFunctionsqrt;
+    Procedure TestFunctionexp;
+    Procedure TestFunctionln;
+    Procedure TestFunctionlog;
+    Procedure TestFunctionfrac;
+    Procedure TestFunctionint;
+    Procedure TestFunctionround;
+    Procedure TestFunctiontrunc;
+    Procedure TestFunctionlength;
+    Procedure TestFunctioncopy;
+    Procedure TestFunctiondelete;
+    Procedure TestFunctionpos;
+    Procedure TestFunctionlowercase;
+    Procedure TestFunctionuppercase;
+    Procedure TestFunctionstringreplace;
+    Procedure TestFunctioncomparetext;
+    Procedure TestFunctiondate;
+    Procedure TestFunctiontime;
+    Procedure TestFunctionnow;
+    Procedure TestFunctiondayofweek;
+    Procedure TestFunctionextractyear;
+    Procedure TestFunctionextractmonth;
+    Procedure TestFunctionextractday;
+    Procedure TestFunctionextracthour;
+    Procedure TestFunctionextractmin;
+    Procedure TestFunctionextractsec;
+    Procedure TestFunctionextractmsec;
+    Procedure TestFunctionencodedate;
+    Procedure TestFunctionencodetime;
+    Procedure TestFunctionencodedatetime;
+    Procedure TestFunctionshortdayname;
+    Procedure TestFunctionshortmonthname;
+    Procedure TestFunctionlongdayname;
+    Procedure TestFunctionlongmonthname;
+    Procedure TestFunctionformatdatetime;
+    Procedure TestFunctionshl;
+    Procedure TestFunctionshr;
+    Procedure TestFunctionIFS;
+    Procedure TestFunctionIFF;
+    Procedure TestFunctionIFD;
+    Procedure TestFunctionIFI;
+    Procedure TestFunctioninttostr;
+    Procedure TestFunctionstrtoint;
+    Procedure TestFunctionstrtointdef;
+    Procedure TestFunctionfloattostr;
+    Procedure TestFunctionstrtofloat;
+    Procedure TestFunctionstrtofloatdef;
+    Procedure TestFunctionbooltostr;
+    Procedure TestFunctionstrtobool;
+    Procedure TestFunctionstrtobooldef;
+    Procedure TestFunctiondatetostr;
+    Procedure TestFunctiontimetostr;
+    Procedure TestFunctionstrtodate;
+    Procedure TestFunctionstrtodatedef;
+    Procedure TestFunctionstrtotime;
+    Procedure TestFunctionstrtotimedef;
+    Procedure TestFunctionstrtodatetime;
+    Procedure TestFunctionstrtodatetimedef;
+  end;
+
+implementation
+
+uses typinfo;
+
+procedure TTestExpressionScanner.TestCreate;
+begin
+  AssertEquals('Empty source','',FP.Source);
+  AssertEquals('Pos is zero',0,FP.Pos);
+  AssertEquals('CurrentChar is zero',#0,FP.CurrentChar);
+  AssertEquals('Current token type is EOF',ttEOF,FP.TokenType);
+  AssertEquals('Current token is empty','',FP.Token);
+end;
+
+procedure TTestExpressionScanner.TestSetSource;
+begin
+  FP.Source:='Abc';
+  FP.Source:='';
+  AssertEquals('Empty source','',FP.Source);
+  AssertEquals('Pos is zero',0,FP.Pos);
+  AssertEquals('CurrentChar is zero',#0,FP.CurrentChar);
+  AssertEquals('Current token type is EOF',ttEOF,FP.TokenType);
+  AssertEquals('Current token is empty','',FP.Token);
+end;
+
+procedure TTestExpressionScanner.TestWhiteSpace;
+begin
+  TestString('  ',ttEOF);
+end;
+
+procedure TTestExpressionScanner.TestTokens;
+
+Const
+  TestStrings : Array[TTokenType] of String
+    = ('+','-','<','>','=','/',
+       '*','(',')','<=','>=',
+       '<>','1','''abc''','abc',',','and',
+       'or','xor','true','false','not','if','case','');
+
+var
+  t : TTokenType;
+
+begin
+  For T:=Low(TTokenType) to High(TTokenType) do
+    TestString(TestStrings[t],t);
+end;
+
+procedure TTestExpressionScanner.TestInvalidNumber;
+
+begin
+  TestString(FInvalidString,ttNumber);
+end;
+
+procedure TTestExpressionScanner.DoInvalidNumber(AString : String);
+
+begin
+  FInvalidString:=AString;
+  AssertException('Invalid number "'+AString+'"',EExprScanner,@TestInvalidNumber);
+end;
+
+procedure TTestExpressionScanner.TestNumber;
+begin
+  TestString('123',ttNumber);
+  TestString('123.4',ttNumber);
+  TestString('123.E4',ttNumber);
+  TestString('1.E4',ttNumber);
+  DoInvalidNumber('1..1');
+  DoInvalidNumber('1.E--1');
+  DoInvalidNumber('.E-1');
+end;
+
+procedure TTestExpressionScanner.TestInvalidCharacter;
+begin
+  DoInvalidNumber('~');
+  DoInvalidNumber('^');
+  DoInvalidNumber('#');
+  DoInvalidNumber('$');
+  DoInvalidNumber('^');
+end;
+
+procedure TTestExpressionScanner.TestUnterminatedString;
+begin
+  DoInvalidNumber('''abc');
+end;
+
+procedure TTestExpressionScanner.TestQuotesInString;
+begin
+  TestString('''That''''s it''',ttString);
+  TestString('''''''s it''',ttString);
+  TestString('''s it''''''',ttString);
+end;
+
+procedure TTestExpressionScanner.SetUp; 
+begin
+  FP:=TFPExpressionScanner.Create;
+end;
+
+procedure TTestExpressionScanner.TearDown; 
+begin
+  FreeAndNil(FP);
+end;
+
+procedure TTestExpressionScanner.AssertEquals(Msg: string; AExpected,
+  AActual: TTokenType);
+
+Var
+  S1,S2 : String;
+
+begin
+  S1:=TokenName(AExpected);
+  S2:=GetEnumName(TypeInfo(TTokenType),Ord(AActual));
+  AssertEquals(Msg,S1,S2);
+end;
+
+procedure TTestExpressionScanner.TestString(const AString: String;
+  AToken: TTokenType);
+begin
+  FP.Source:=AString;
+  AssertEquals('String "'+AString+'" results in token '+TokenName(AToken),AToken,FP.GetToken);
+  If Not (FP.TokenType in [ttString,ttEOF]) then
+    AssertEquals('String "'+AString+'" results in token string '+TokenName(AToken),AString,FP.Token)
+  else if FP.TokenType=ttString then
+    AssertEquals('String "'+AString+'" results in token string '+TokenName(AToken),
+                  StringReplace(AString,'''''','''',[rfreplaceAll]),
+                  ''''+FP.Token+'''');
+end;
+
+{ TTestBaseParser }
+
+procedure TTestBaseParser.DoCheck;
+begin
+  FCheckNode.Check;
+end;
+
+procedure TTestBaseParser.AssertNodeType(Msg: String; AClass: TClass;
+  ANode: TFPExprNode);
+begin
+  AssertNotNull(Msg+': Not null',ANode);
+  AssertEquals(Msg+': Class OK',AClass,ANode.ClassType);
+end;
+
+procedure TTestBaseParser.AssertEquals(Msg: String; AResultType: TResultType;
+  ANode: TFPExprNode);
+begin
+  AssertNotNull(Msg+': Node not null',ANode);
+  AssertEquals(Msg,AResultType,Anode.NodeType);
+end;
+
+procedure TTestBaseParser.AssertEquals(Msg: String; AExpected,
+  AActual: TResultType);
+
+begin
+  AssertEquals(Msg,ResultTypeName(AExpected),ResultTypeName(AActual));
+end;
+
+function TTestBaseParser.CreateIntNode(AInteger: Integer): TFPExprNode;
+begin
+  Result:=TFPConstExpression.CreateInteger(AInteger);
+end;
+
+function TTestBaseParser.CreateFloatNode(AFloat: TExprFloat): TFPExprNode;
+begin
+  Result:=TFPConstExpression.CreateFloat(AFloat);
+end;
+
+function TTestBaseParser.CreateStringNode(Astring: String): TFPExprNode;
+begin
+  Result:=TFPConstExpression.CreateString(AString);
+end;
+
+function TTestBaseParser.CreateDateTimeNode(ADateTime: TDateTime): TFPExprNode;
+begin
+  Result:=TFPConstExpression.CreateDateTime(ADateTime);
+end;
+
+procedure TTestBaseParser.AssertNodeOK(FN: TFPExprNode);
+
+Var
+  B : Boolean;
+  Msg : String;
+
+begin
+  AssertNotNull('Node to test OK',FN);
+  B:=False;
+  try
+    FN.Check;
+    B:=True;
+  except
+    On E : Exception do
+      Msg:=E.Message;
+  end;
+  If Not B then
+    Fail(Format('Node %s not OK: %s',[FN.ClassName,Msg]));
+end;
+
+procedure TTestBaseParser.AssertNodeNotOK(const MSg : String; FN: TFPExprNode);
+begin
+  FCheckNode:=FN;
+  AssertException(Msg,EExprParser,@DoCheck);
+end;
+
+function TTestBaseParser.CreateBoolNode(ABoolean: Boolean): TFPExprNode;
+begin
+  Result:=TFPConstExpression.CreateBoolean(ABoolean);
+end;
+
+procedure TTestBaseParser.Setup;
+begin
+  inherited Setup;
+  FDestroyCalled:=0;
+end;
+
+
+{ TTestConstExprNode }
+
+procedure TTestConstExprNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestConstExprNode.TestCreateInteger;
+begin
+  FN:=TFPConstExpression.CreateInteger(1);
+  AssertEquals('Correct type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',1,FN.ConstValue.ResInteger);
+  AssertEquals('Correct result',1,FN.NodeValue.ResInteger);
+  AssertEquals('AsString ok','1',FN.AsString);
+end;
+
+procedure TTestConstExprNode.TestCreateFloat;
+
+Var
+  S : String;
+
+begin
+  FN:=TFPConstExpression.CreateFloat(2.34);
+  AssertEquals('Correct type',rtFloat,FN.NodeType);
+  AssertEquals('Correct result',2.34,FN.ConstValue.ResFloat);
+  AssertEquals('Correct result',2.34,FN.NodeValue.ResFloat);
+  Str(TExprFLoat(2.34),S);
+  AssertEquals('AsString ok',S,FN.AsString);
+end;
+
+procedure TTestConstExprNode.TestCreateBoolean;
+begin
+  FN:=TFPConstExpression.CreateBoolean(True);
+  AssertEquals('Correct type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',True,FN.ConstValue.ResBoolean);
+  AssertEquals('Correct result',True,FN.NodeValue.ResBoolean);
+  AssertEquals('AsString ok','True',FN.AsString);
+  FreeAndNil(FN);
+  FN:=TFPConstExpression.CreateBoolean(False);
+  AssertEquals('AsString ok','False',FN.AsString);
+end;
+
+procedure TTestConstExprNode.TestCreateDateTime;
+
+Var
+  D : TDateTime;
+  S : String;
+
+begin
+  D:=Now;
+  FN:=TFPConstExpression.CreateDateTime(D);
+  AssertEquals('Correct type',rtDateTime,FN.NodeType);
+  AssertEquals('Correct result',D,FN.ConstValue.ResDateTime);
+  AssertEquals('Correct result',D,FN.NodeValue.ResDateTime);
+  S:=''''+FormatDateTime('cccc',D)+'''';
+  AssertEquals('AsString ok',S,FN.AsString);
+end;
+
+procedure TTestConstExprNode.TestCreateString;
+
+Var
+  S : String;
+
+begin
+  S:='Ohlala';
+  FN:=TFPConstExpression.CreateString(S);
+  AssertEquals('Correct type',rtString,FN.NodeType);
+  AssertEquals('Correct result',S,FN.ConstValue.ResString);
+  AssertEquals('Correct result',S,FN.NodeValue.ResString);
+  AssertEquals('AsString ok',''''+S+'''',FN.AsString);
+end;
+
+{ TTestNegateExprNode }
+
+procedure TTestNegateExprNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestNegateExprNode.TestCreateInteger;
+
+begin
+  FN:=TFPNegateOperation.Create(CreateIntNode(23));
+  AssertEquals('Negate has correct type',rtInteger,FN.NodeType);
+  AssertEquals('Negate has correct result',-23,FN.NodeValue.Resinteger);
+  AssertEquals('Negate has correct string','-23',FN.AsString);
+  AssertNodeOK(FN);
+end;
+
+
+procedure TTestNegateExprNode.TestCreateFloat;
+
+Var
+  S : String;
+
+begin
+  FN:=TFPNegateOperation.Create(CreateFloatNode(1.23));
+  AssertEquals('Negate has correct type',rtFloat,FN.NodeType);
+  AssertEquals('Negate has correct result',-1.23,FN.NodeValue.ResFloat);
+  Str(TExprFloat(-1.23),S);
+  AssertEquals('Negate has correct string',S,FN.AsString);
+  AssertNodeOK(FN);
+end;
+
+procedure TTestNegateExprNode.TestCreateOther1;
+
+begin
+  FN:=TFPNegateOperation.Create(TFPConstExpression.CreateString('1.23'));
+  AssertNodeNotOK('Negate does not accept string',FN);
+end;
+
+procedure TTestNegateExprNode.TestCreateOther2;
+
+begin
+  FN:=TFPNegateOperation.Create(TFPConstExpression.CreateBoolean(True));
+  AssertNodeNotOK('Negate does not accept boolean',FN)
+end;
+
+procedure TTestNegateExprNode.TestDestroy;
+begin
+  FN:=TFPNegateOperation.Create(TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Operand Destroy called',1,self.FDestroyCalled)
+end;
+
+{ TTestDestroyNode }
+
+procedure TTestDestroyNode.TestDestroy;
+
+Var
+  FN : TMyDestroyNode;
+
+begin
+  AssertEquals('Destroy not called yet',0,self.FDestroyCalled);
+  FN:=TMyDestroyNode.CreateTest(Self);
+  FN.Free;
+  AssertEquals('Destroy called',1,self.FDestroyCalled)
+end;
+
+{ TMyDestroyNode }
+
+constructor TMyDestroyNode.CreateTest(ATest: TTestBaseParser);
+begin
+  FTest:=ATest;
+  Inherited CreateInteger(1);
+end;
+
+destructor TMyDestroyNode.Destroy;
+begin
+  Inc(FTest.FDestroyCalled);
+  inherited Destroy;
+end;
+
+{ TTestBinaryAndNode }
+
+procedure TTestBinaryAndNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestBinaryAndNode.TestCreateInteger;
+begin
+  FN:=TFPBinaryAndOperation.Create(CreateIntNode(3),CreateIntNode(2));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',2,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestBinaryAndNode.TestCreateBoolean;
+begin
+  FN:=TFPBinaryAndOperation.Create(CreateBoolNode(True),CreateBoolNode(True));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',True,FN.NodeValue.ResBoolean);
+end;
+
+procedure TTestBinaryAndNode.TestCreateBooleanInteger;
+begin
+  FN:=TFPBinaryAndOperation.Create(CreateBoolNode(True),CreateIntNode(0));
+  AssertNodeNotOK('Different node types',FN);
+end;
+
+procedure TTestBinaryAndNode.TestCreateString;
+begin
+  FN:=TFPBinaryAndOperation.Create(CreateStringNode('True'),CreateStringNode('True'));
+  AssertNodeNotOK('String node type',FN);
+end;
+
+procedure TTestBinaryAndNode.TestCreateFloat;
+begin
+  FN:=TFPBinaryAndOperation.Create(CreateFloatNode(1.23),CreateFloatNode(1.23));
+  AssertNodeNotOK('float node type',FN);
+end;
+
+procedure TTestBinaryAndNode.TestCreateDateTime;
+begin
+  FN:=TFPBinaryAndOperation.Create(CreateDateTimeNode(Now),CreateDateTimeNode(Now));
+  AssertNodeNotOK('DateTime node type',FN);
+end;
+
+procedure TTestBinaryAndNode.TestDestroy;
+begin
+  FN:=TFPBinaryAndOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+{ TTestBinaryOrNode }
+
+procedure TTestBinaryOrNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestBinaryOrNode.TestCreateInteger;
+begin
+  FN:=TFPBinaryOrOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',3,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestBinaryOrNode.TestCreateBoolean;
+begin
+  FN:=TFPBinaryOrOperation.Create(CreateBoolNode(True),CreateBoolNode(False));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',True,FN.NodeValue.ResBoolean);
+end;
+
+procedure TTestBinaryOrNode.TestCreateBooleanInteger;
+begin
+  FN:=TFPBinaryOrOperation.Create(CreateBoolNode(True),CreateIntNode(0));
+  AssertNodeNotOK('Different node types',FN);
+end;
+
+procedure TTestBinaryOrNode.TestCreateString;
+begin
+  FN:=TFPBinaryOrOperation.Create(CreateStringNode('True'),CreateStringNode('True'));
+  AssertNodeNotOK('String node type',FN);
+end;
+
+procedure TTestBinaryOrNode.TestCreateFloat;
+begin
+  FN:=TFPBinaryOrOperation.Create(CreateFloatNode(1.23),CreateFloatNode(1.23));
+  AssertNodeNotOK('float node type',FN);
+end;
+
+procedure TTestBinaryOrNode.TestCreateDateTime;
+begin
+  FN:=TFPBinaryOrOperation.Create(CreateDateTimeNode(Now),CreateDateTimeNode(Now));
+  AssertNodeNotOK('DateTime node type',FN);
+end;
+
+procedure TTestBinaryOrNode.TestDestroy;
+begin
+  FN:=TFPBinaryOrOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+{ TTestBinaryXorNode }
+
+procedure TTestBinaryXorNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestBinaryXorNode.TestCreateInteger;
+begin
+  FN:=TFPBinaryXorOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',3,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestBinaryXorNode.TestCreateBoolean;
+begin
+  FN:=TFPBinaryXorOperation.Create(CreateBoolNode(True),CreateBoolNode(True));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',False,FN.NodeValue.ResBoolean);
+end;
+
+procedure TTestBinaryXorNode.TestCreateBooleanInteger;
+begin
+  FN:=TFPBinaryXorOperation.Create(CreateBoolNode(True),CreateIntNode(0));
+  AssertNodeNotOK('Different node types',FN);
+end;
+
+procedure TTestBinaryXorNode.TestCreateString;
+begin
+  FN:=TFPBinaryXorOperation.Create(CreateStringNode('True'),CreateStringNode('True'));
+  AssertNodeNotOK('String node type',FN);
+end;
+
+procedure TTestBinaryXorNode.TestCreateFloat;
+begin
+  FN:=TFPBinaryXorOperation.Create(CreateFloatNode(1.23),CreateFloatNode(1.23));
+  AssertNodeNotOK('float node type',FN);
+end;
+
+procedure TTestBinaryXorNode.TestCreateDateTime;
+begin
+  FN:=TFPBinaryXorOperation.Create(CreateDateTimeNode(Now),CreateDateTimeNode(Now));
+  AssertNodeNotOK('DateTime node type',FN);
+end;
+
+procedure TTestBinaryXorNode.TestDestroy;
+begin
+  FN:=TFPBinaryXorOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+{ TTestBooleanNode }
+
+procedure TTestBooleanNode.TestNode(B: TFPBooleanResultOperation;
+  AResult: Boolean);
+begin
+  AssertEquals(Format('Test %s(%s,%s) result',[B.ClassName,B.Left.AsString,B.Right.AsString]),Aresult,B.NodeValue.resBoolean);
+end;
+
+{ TTestEqualNode }
+
+procedure TTestEqualNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+class function TTestEqualNode.NodeClass: TFPBooleanResultOperationClass;
+begin
+  Result:=TFPEqualOperation;
+end;
+
+class function TTestEqualNode.ExpectedResult: Boolean;
+begin
+  Result:=True
+end;
+
+class function TTestEqualNode.OperatorString: String;
+begin
+  Result:='=';
+end;
+
+procedure TTestEqualNode.TestCreateIntegerEqual;
+begin
+  FN:=NodeClass.Create(CreateIntNode(1),CreateIntNode(1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateIntegerUnEqual;
+begin
+  FN:=NodeClass.Create(CreateIntNode(2),CreateIntNode(1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateFloatEqual;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.23),CreateFloatNode(1.23));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateFloatUnEqual;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.23),CreateFloatNode(1.34));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateStringEqual;
+begin
+  FN:=NodeClass.Create(CreateStringNode('now'),CreateStringNode('now'));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateStringUnEqual;
+begin
+  FN:=NodeClass.Create(CreateStringNode('now'),CreateStringNode('then'));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateBooleanEqual;
+begin
+  FN:=NodeClass.Create(CreateBoolNode(True),CreateBoolNode(True));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateBooleanUnEqual;
+begin
+  FN:=NodeClass.Create(CreateBoolNode(False),CreateBoolNode(True));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateDateTimeEqual;
+
+Var
+  D : TDateTime;
+
+begin
+  D:=Now;
+  FN:=NodeClass.Create(CreateDateTimeNode(D),CreateDateTimeNode(D));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,ExpectedResult);
+end;
+
+procedure TTestEqualNode.TestCreateDateTimeUnEqual;
+
+Var
+  D : TDateTime;
+
+begin
+  D:=Now;
+  FN:=NodeClass.Create(CreateDateTimeNode(D),CreateDateTimeNode(D-1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not ExpectedResult);
+end;
+
+
+procedure TTestEqualNode.TestDestroy;
+begin
+  FN:=NodeClass.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+procedure TTestEqualNode.TestWrongTypes1;
+begin
+  FN:=NodeClass.Create(CreateIntNode(3),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestEqualNode.TestWrongTypes2;
+begin
+  FN:=NodeClass.Create(CreateDateTimeNode(3),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestEqualNode.TestWrongTypes3;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.3),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestEqualNode.TestWrongTypes4;
+begin
+  FN:=NodeClass.Create(CreateBoolNode(False),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestEqualNode.TestWrongTypes5;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1),CreateIntNode(1));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+
+procedure TTestEqualNode.TestAsString;
+begin
+  FN:=NodeClass.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Asstring works ok','1 '+OPeratorString+' 2',FN.AsString);
+end;
+
+{ TTestUnEqualNode }
+
+class function TTestUnEqualNode.NodeClass: TFPBooleanResultOperationClass;
+begin
+  Result:=TFPUnEqualOperation;
+end;
+
+class function TTestUnEqualNode.ExpectedResult: Boolean;
+begin
+  Result:=False;
+end;
+
+class function TTestUnEqualNode.OperatorString: String;
+begin
+  Result:='<>';
+end;
+
+{ TTestLessThanNode }
+
+class function TTestLessThanNode.NodeClass: TFPBooleanResultOperationClass;
+begin
+  Result:=TFPLessThanOperation;
+end;
+
+class function TTestLessThanNode.Larger: Boolean;
+begin
+  Result:=False;
+end;
+
+class function TTestLessThanNode.AllowEqual: Boolean;
+begin
+  Result:=False;
+end;
+
+class function TTestLessThanNode.OperatorString: String;
+begin
+  Result:='<';
+end;
+
+procedure TTestLessThanNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestLessThanNode.TestCreateIntegerEqual;
+begin
+  FN:=NodeClass.Create(CreateIntNode(1),CreateIntNode(1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,AllowEqual);
+end;
+
+procedure TTestLessThanNode.TestCreateIntegerSmaller;
+begin
+  FN:=NodeClass.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not Larger);
+end;
+
+procedure TTestLessThanNode.TestCreateIntegerLarger;
+begin
+  FN:=NodeClass.Create(CreateIntNode(2),CreateIntNode(1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Larger);
+end;
+
+procedure TTestLessThanNode.TestCreateFloatEqual;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.23),CreateFloatNode(1.23));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,AllowEqual);
+end;
+
+procedure TTestLessThanNode.TestCreateFloatSmaller;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.23),CreateFloatNode(4.56));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not Larger);
+end;
+
+procedure TTestLessThanNode.TestCreateFloatLarger;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(4.56),CreateFloatNode(1.23));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Larger);
+end;
+
+procedure TTestLessThanNode.TestCreateDateTimeEqual;
+
+Var
+  D : TDateTime;
+
+begin
+  D:=Now;
+  FN:=NodeClass.Create(CreateDateTimeNode(D),CreateDateTimeNode(D));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,AllowEqual);
+end;
+
+procedure TTestLessThanNode.TestCreateDateTimeSmaller;
+
+Var
+  D : TDateTime;
+
+begin
+  D:=Now;
+  FN:=NodeClass.Create(CreateDateTimeNode(D),CreateDateTimeNode(D+1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not larger);
+end;
+
+procedure TTestLessThanNode.TestCreateDateTimeLarger;
+
+Var
+  D : TDateTime;
+
+begin
+  D:=Now;
+  FN:=NodeClass.Create(CreateDateTimeNode(D),CreateDateTimeNode(D-1));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,larger);
+end;
+
+procedure TTestLessThanNode.TestCreateStringEqual;
+begin
+  FN:=NodeClass.Create(CreateStringNode('now'),CreateStringNode('now'));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,AllowEqual);
+end;
+
+procedure TTestLessThanNode.TestCreateStringSmaller;
+begin
+  FN:=NodeClass.Create(CreateStringNode('now'),CreateStringNode('then'));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Not Larger);
+end;
+
+procedure TTestLessThanNode.TestCreateStringLarger;
+begin
+  FN:=NodeClass.Create(CreateStringNode('then'),CreateStringNode('now'));
+  AssertNodeOk(FN);
+  AssertEquals('Boolean result',rtBoolean,FN.NodeType);
+  TestNode(FN,Larger);
+end;
+
+procedure TTestLessThanNode.TestWrongTypes1;
+begin
+  FN:=NodeClass.Create(CreateIntNode(3),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestWrongTypes2;
+begin
+  FN:=NodeClass.Create(CreateDateTimeNode(3),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestWrongTypes3;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.3),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestWrongTypes4;
+begin
+  FN:=NodeClass.Create(CreateBoolNode(False),CreateStringNode('1.23'));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestWrongTypes5;
+begin
+  FN:=NodeClass.Create(CreateFloatNode(1.23),CreateIntNode(1));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestNoBoolean1;
+begin
+  FN:=NodeClass.Create(CreateBoolNode(False),CreateIntNode(1));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestNoBoolean2;
+begin
+  FN:=NodeClass.Create(CreateIntNode(1),CreateBoolNode(False));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestNoBoolean3;
+begin
+  FN:=NodeClass.Create(CreateBoolNode(False),CreateBoolNode(False));
+  AssertNodeNotOk('Wrong Types',FN);
+end;
+
+procedure TTestLessThanNode.TestAsString;
+begin
+  FN:=NodeClass.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Asstring works ok','1 '+OPeratorString+' 2',FN.AsString);
+end;
+
+{ TTestLessThanEqualNode }
+
+class function TTestLessThanEqualNode.NodeClass: TFPBooleanResultOperationClass;
+begin
+  Result:=TFPLessThanEqualOperation;
+end;
+
+class function TTestLessThanEqualNode.AllowEqual: Boolean;
+begin
+  Result:=True;
+end;
+
+class function TTestLessThanEqualNode.OperatorString: String;
+begin
+  Result:='<=';
+end;
+
+{ TTestLargerThanNode }
+
+class function TTestLargerThanNode.NodeClass: TFPBooleanResultOperationClass;
+begin
+  Result:=TFPGreaterThanOperation;
+end;
+
+class function TTestLargerThanNode.Larger: Boolean;
+begin
+  Result:=True;
+end;
+
+class function TTestLargerThanNode.OperatorString: String;
+begin
+  Result:='>';
+end;
+
+{ TTestLargerThanEqualNode }
+
+class function TTestLargerThanEqualNode.NodeClass: TFPBooleanResultOperationClass;
+begin
+  Result:=TFPGreaterThanEqualOperation;
+end;
+
+class function TTestLargerThanEqualNode.AllowEqual: Boolean;
+begin
+  Result:=True;
+end;
+
+class function TTestLargerThanEqualNode.OperatorString: String;
+begin
+  Result:='>=';
+end;
+
+{ TTestAddNode }
+
+procedure TTestAddNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestAddNode.TestCreateInteger;
+begin
+  FN:=TFPAddOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Add has correct type',rtInteger,FN.NodeType);
+  AssertEquals('Add has correct result',3,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestAddNode.TestCreateFloat;
+begin
+  FN:=TFPAddOperation.Create(CreateFloatNode(1.23),CreateFloatNode(4.56));
+  AssertEquals('Add has correct type',rtFloat,FN.NodeType);
+  AssertEquals('Add has correct result',5.79,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestAddNode.TestCreateDateTime;
+
+Var
+  D,T : TDateTime;
+
+begin
+  D:=Date;
+  T:=Time;
+  FN:=TFPAddOperation.Create(CreateDateTimeNode(D),CreateDateTimeNode(T));
+  AssertEquals('Add has correct type',rtDateTime,FN.NodeType);
+  AssertEquals('Add has correct result',D+T,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestAddNode.TestCreateString;
+begin
+  FN:=TFPAddOperation.Create(CreateStringNode('alo'),CreateStringNode('ha'));
+  AssertEquals('Add has correct type',rtString,FN.NodeType);
+  AssertEquals('Add has correct result','aloha',FN.NodeValue.ResString);
+end;
+
+procedure TTestAddNode.TestCreateBoolean;
+begin
+  FN:=TFPAddOperation.Create(CreateBoolNode(True),CreateBoolNode(False));
+  AssertNodeNotOK('No boolean addition',FN);
+end;
+
+procedure TTestAddNode.TestDestroy;
+begin
+  FN:=TFPAddOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+procedure TTestAddNode.TestAsString;
+begin
+  FN:=TFPAddOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Asstring works ok','1 + 2',FN.AsString);
+end;
+
+{ TTestSubtractNode }
+
+procedure TTestSubtractNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestSubtractNode.TestCreateInteger;
+begin
+  FN:=TFPSubtractOperation.Create(CreateIntNode(4),CreateIntNode(1));
+  AssertEquals('Subtract has correct type',rtInteger,FN.NodeType);
+  AssertEquals('Subtract has correct result',3,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestSubtractNode.TestCreateFloat;
+begin
+  FN:=TFPSubtractOperation.Create(CreateFloatNode(4.56),CreateFloatNode(1.23));
+  AssertEquals('Subtract has correct type',rtFloat,FN.NodeType);
+  AssertEquals('Subtract has correct result',3.33,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestSubtractNode.TestCreateDateTime;
+
+Var
+  D,T : TDateTime;
+
+begin
+  D:=Date;
+  T:=Time;
+  FN:=TFPSubtractOperation.Create(CreateDateTimeNode(D+T),CreateDateTimeNode(T));
+  AssertEquals('Subtract has correct type',rtDateTime,FN.NodeType);
+  AssertEquals('Subtract has correct result',D,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestSubtractNode.TestCreateString;
+begin
+  FN:=TFPSubtractOperation.Create(CreateStringNode('alo'),CreateStringNode('ha'));
+  AssertNodeNotOK('No string Subtract',FN);
+end;
+
+procedure TTestSubtractNode.TestCreateBoolean;
+begin
+  FN:=TFPSubtractOperation.Create(CreateBoolNode(True),CreateBoolNode(False));
+  AssertNodeNotOK('No boolean Subtract',FN);
+end;
+
+procedure TTestSubtractNode.TestDestroy;
+begin
+  FN:=TFPSubtractOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+procedure TTestSubtractNode.TestAsString;
+begin
+  FN:=TFPSubtractOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Asstring works ok','1 - 2',FN.AsString);
+end;
+
+{ TTestMultiplyNode }
+
+procedure TTestMultiplyNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestMultiplyNode.TestCreateInteger;
+begin
+  FN:=TFPMultiplyOperation.Create(CreateIntNode(4),CreateIntNode(2));
+  AssertEquals('multiply has correct type',rtInteger,FN.NodeType);
+  AssertEquals('multiply has correct result',8,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestMultiplyNode.TestCreateFloat;
+begin
+  FN:=TFPMultiplyOperation.Create(CreateFloatNode(2.0),CreateFloatNode(1.23));
+  AssertEquals('multiply has correct type',rtFloat,FN.NodeType);
+  AssertEquals('multiply has correct result',2.46,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestMultiplyNode.TestCreateDateTime;
+
+Var
+  D,T : TDateTime;
+
+begin
+  D:=Date;
+  T:=Time;
+  FN:=TFPMultiplyOperation.Create(CreateDateTimeNode(D+T),CreateDateTimeNode(T));
+  AssertNodeNotOK('No datetime multiply',FN);
+end;
+
+procedure TTestMultiplyNode.TestCreateString;
+begin
+  FN:=TFPMultiplyOperation.Create(CreateStringNode('alo'),CreateStringNode('ha'));
+  AssertNodeNotOK('No string multiply',FN);
+end;
+
+procedure TTestMultiplyNode.TestCreateBoolean;
+begin
+  FN:=TFPMultiplyOperation.Create(CreateBoolNode(True),CreateBoolNode(False));
+  AssertNodeNotOK('No boolean multiply',FN);
+end;
+
+procedure TTestMultiplyNode.TestDestroy;
+begin
+  FN:=TFPMultiplyOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+procedure TTestMultiplyNode.TestAsString;
+begin
+  FN:=TFPMultiplyOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Asstring works ok','1 * 2',FN.AsString);
+end;
+
+
+{ TTestDivideNode }
+
+procedure TTestDivideNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestDivideNode.TestCreateInteger;
+begin
+  FN:=TFPDivideOperation.Create(CreateIntNode(4),CreateIntNode(2));
+  AssertEquals('Divide has correct type',rtfloat,FN.NodeType);
+  AssertEquals('Divide has correct result',2.0,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestDivideNode.TestCreateFloat;
+begin
+  FN:=TFPDivideOperation.Create(CreateFloatNode(9.0),CreateFloatNode(3.0));
+  AssertEquals('Divide has correct type',rtFloat,FN.NodeType);
+  AssertEquals('Divide has correct result',3.0,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestDivideNode.TestCreateDateTime;
+
+Var
+  D,T : TDateTime;
+
+begin
+  D:=Date;
+  T:=Time;
+  FN:=TFPDivideOperation.Create(CreateDateTimeNode(D+T),CreateDateTimeNode(T));
+  AssertNodeNotOK('No datetime division',FN);
+end;
+
+procedure TTestDivideNode.TestCreateString;
+begin
+  FN:=TFPDivideOperation.Create(CreateStringNode('alo'),CreateStringNode('ha'));
+  AssertNodeNotOK('No string division',FN);
+end;
+
+procedure TTestDivideNode.TestCreateBoolean;
+begin
+  FN:=TFPDivideOperation.Create(CreateBoolNode(True),CreateBoolNode(False));
+  AssertNodeNotOK('No boolean division',FN);
+end;
+
+procedure TTestDivideNode.TestDestroy;
+begin
+  FN:=TFPDivideOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',2,self.FDestroyCalled)
+end;
+
+procedure TTestDivideNode.TestAsString;
+begin
+  FN:=TFPDivideOperation.Create(CreateIntNode(1),CreateIntNode(2));
+  AssertEquals('Asstring works ok','1 / 2',FN.AsString);
+end;
+
+{ TTestIntToFloatNode }
+
+procedure TTestIntToFloatNode.TearDown;
+begin
+  FreeAndNil(Fn);
+  inherited TearDown;
+end;
+
+procedure TTestIntToFloatNode.TestCreateInteger;
+begin
+  FN:=TIntToFloatNode.Create(CreateIntNode(4));
+  AssertEquals('Convert has correct type',rtfloat,FN.NodeType);
+  AssertEquals('Convert has correct result',4.0,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestIntToFloatNode.TestCreateFloat;
+begin
+  FN:=TIntToFloatNode.Create(CreateFloatNode(4.0));
+  AssertNodeNotOK('No float allowed',FN);
+end;
+
+procedure TTestIntToFloatNode.TestDestroy;
+begin
+  FN:=TIntToFloatNode.Create(TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',1,self.FDestroyCalled)
+end;
+
+procedure TTestIntToFloatNode.TestAsString;
+begin
+  FN:=TIntToFloatNode.Create(CreateIntNode(4));
+  AssertEquals('Convert has correct asstring','4',FN.AsString);
+end;
+
+{ TTestIntToDateTimeNode }
+
+procedure TTestIntToDateTimeNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestIntToDateTimeNode.TestCreateInteger;
+begin
+  FN:=TIntToDateTimeNode.Create(CreateIntNode(Round(Date)));
+  AssertEquals('Convert has correct type',rtDateTime,FN.NodeType);
+  AssertEquals('Convert has correct result',Date,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestIntToDateTimeNode.TestCreateFloat;
+begin
+  FN:=TIntToDateTimeNode.Create(CreateFloatNode(4.0));
+  AssertNodeNotOK('No float allowed',FN);
+end;
+
+procedure TTestIntToDateTimeNode.TestDestroy;
+begin
+  FN:=TIntToDateTimeNode.Create(TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',1,self.FDestroyCalled)
+end;
+
+procedure TTestIntToDateTimeNode.TestAsString;
+begin
+  FN:=TIntToDateTimeNode.Create(CreateIntNode(4));
+  AssertEquals('Convert has correct asstring','4',FN.AsString);
+end;
+
+{ TTestFloatToDateTimeNode }
+
+procedure TTestFloatToDateTimeNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestFloatToDateTimeNode.TestCreateInteger;
+begin
+  FN:=TFloatToDateTimeNode.Create(CreateIntNode(4));
+  AssertNodeNotOK('No int allowed',FN);
+end;
+
+procedure TTestFloatToDateTimeNode.TestCreateFloat;
+
+Var
+  T : TExprFloat;
+
+begin
+  T:=Time;
+  FN:=TFloatToDateTimeNode.Create(CreateFloatNode(T));
+  AssertEquals('Convert has correct type',rtDateTime,FN.NodeType);
+  AssertEquals('Convert has correct result',T,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestFloatToDateTimeNode.TestDestroy;
+begin
+  FN:=TFloatToDateTimeNode.Create(TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for left and right nodes',1,self.FDestroyCalled)
+end;
+
+procedure TTestFloatToDateTimeNode.TestAsString;
+
+Var
+  S : String;
+
+begin
+  FN:=TFloatToDateTimeNode.Create(CreateFloatNode(1.2));
+  Str(TExprFloat(1.2),S);
+  AssertEquals('Convert has correct asstring',S,FN.AsString);
+end;
+
+{ TMyFPExpressionParser }
+
+procedure TMyFPExpressionParser.BuildHashList;
+begin
+  CreateHashList;
+end;
+
+{ TTestExpressionParser }
+
+procedure TTestExpressionParser.SetUp;
+begin
+  inherited SetUp;
+  FP:=TMyFPExpressionParser.Create(Nil);
+end;
+
+procedure TTestExpressionParser.TearDown;
+begin
+  FreeAndNil(FP);
+  inherited TearDown;
+end;
+
+procedure TTestExpressionParser.DoParse;
+
+begin
+  FP.Expression:=FTestExpr;
+end;
+
+procedure TTestExpressionParser.TestParser(AExpr : string);
+
+begin
+  FTestExpr:=AExpr;
+  AssertException(Format('Wrong expression: "%s"',[AExpr]),EExprParser,@DoParse);
+end;
+
+procedure TTestExpressionParser.AssertLeftRight(N: TFPExprNode; LeftClass,
+  RightClass: TClass);
+begin
+  AssertNotNull('Binary node not null',N);
+  If Not N.InheritsFrom(TFPBinaryOperation) then
+    Fail(N.ClassName+' does not descend from TFPBinaryOperation');
+  AssertNotNull('Left node assigned',TFPBinaryOperation(N).Left);
+  AssertNotNull('Right node assigned',TFPBinaryOperation(N).Right);
+  AssertEquals('Left node correct class ',LeftClass, TFPBinaryOperation(N).Left.ClassType);
+  AssertEquals('Right node correct class ',RightClass, TFPBinaryOperation(N).Right.ClassType);
+end;
+
+procedure TTestExpressionParser.AssertOperand(N: TFPExprNode;
+  OperandClass: TClass);
+begin
+  AssertNotNull('Unary node not null',N);
+  If Not N.InheritsFrom(TFPUnaryOperator) then
+    Fail(N.ClassName+' does not descend from TFPUnaryOperator');
+  AssertNotNull('Operand assigned',TFPUnaryOperator(N).Operand);
+  AssertEquals('Operand node correct class ',OperandClass, TFPUnaryOperator(N).Operand.ClassType);
+end;
+
+procedure TTestExpressionParser.AssertResultType(RT: TResultType);
+begin
+  AssertEquals('Result type is '+ResultTypeName(rt),rt,FP.ExprNode);
+  AssertEquals('Result type is '+ResultTypeName(rt),rt,FP.ResultType);
+end;
+
+procedure TTestExpressionParser.AssertResult(F: TExprFloat);
+begin
+  AssertEquals('Correct float result',F,FP.ExprNode.NodeValue.ResFloat);
+  AssertEquals('Correct float result',F,FP.Evaluate.ResFloat);
+end;
+
+procedure TTestExpressionParser.AssertResult(I: Int64);
+begin
+  AssertEquals('Correct integer result',I,FP.ExprNode.NodeValue.ResInteger);
+  AssertEquals('Correct integer result',I,FP.Evaluate.ResInteger);
+end;
+
+procedure TTestExpressionParser.AssertResult(S: String);
+begin
+  AssertEquals('Correct string result',S,FP.ExprNode.NodeValue.ResString);
+  AssertEquals('Correct string result',S,FP.Evaluate.ResString);
+end;
+
+procedure TTestExpressionParser.AssertResult(B: Boolean);
+begin
+  AssertEquals('Correct boolean result',B,FP.ExprNode.NodeValue.ResBoolean);
+  AssertEquals('Correct boolean result',B,FP.Evaluate.ResBoolean);
+end;
+
+procedure TTestExpressionParser.AssertDateTimeResult(D: TDateTime);
+begin
+  AssertEquals('Correct datetime result',D,FP.ExprNode.NodeValue.ResDateTime);
+  AssertEquals('Correct boolean result',D,FP.Evaluate.ResDateTime);
+end;
+//TTestParserExpressions
+procedure TTestParserExpressions.TestCreate;
+begin
+  AssertEquals('Expression is empty','',FP.Expression);
+  AssertNotNull('Identifiers assigned',FP.Identifiers);
+  AssertEquals('No identifiers',0,FP.Identifiers.Count);
+end;
+
+
+procedure TTestParserExpressions.TestSimpleNodeFloat;
+begin
+  FP.Expression:='123.4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, FP.ExprNode);
+  AssertResultType(rtFloat);
+  AssertResult(123.4);
+end;
+
+procedure TTestParserExpressions.TestSimpleNodeInteger;
+begin
+  FP.Expression:='1234';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(1234);
+end;
+
+procedure TTestParserExpressions.TestSimpleNodeBooleanTrue;
+begin
+  FP.Expression:='true';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, FP.ExprNode);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserExpressions.TestSimpleNodeBooleanFalse;
+begin
+  FP.Expression:='False';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, FP.ExprNode);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserExpressions.TestSimpleNodeString;
+begin
+  FP.Expression:='''A string''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, FP.ExprNode);
+  AssertResultType(rtString);
+  AssertResult('A string');
+end;
+
+procedure TTestParserExpressions.TestSimpleNegativeInteger;
+begin
+  FP.Expression:='-1234';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPNegateOperation, FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, TFPNegateOperation(FP.ExprNode).Operand);
+  AssertResultType(rtInteger);
+  AssertResult(-1234);
+end;
+
+procedure TTestParserExpressions.TestSimpleNegativeFloat;
+begin
+  FP.Expression:='-1.234';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPNegateOperation, FP.ExprNode);
+  AssertNodeType('Constant expression',TFPConstExpression, TFPNegateOperation(FP.ExprNode).Operand);
+  AssertResultType(rtFloat);
+  AssertResult(-1.234);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddInteger;
+begin
+  FP.Expression:='4+1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(5);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddFloat;
+begin
+  FP.Expression:='1.2+3.4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(4.6);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddIntegerFloat;
+begin
+  FP.Expression:='1+3.4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TIntToFLoatNode,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(4.4);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddFloatInteger;
+begin
+  FP.Expression:='3.4 + 1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TIntToFLoatNode);
+  AssertResultType(rtFloat);
+  AssertResult(4.4);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddString;
+begin
+  FP.Expression:='''alo''+''ha''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtString);
+  AssertResult('aloha');
+end;
+
+procedure TTestParserExpressions.TestSimpleSubtractInteger;
+begin
+  FP.Expression:='4-1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPSubtractOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(3);
+end;
+
+procedure TTestParserExpressions.TestSimpleSubtractFloat;
+begin
+  FP.Expression:='3.4-1.2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPSubtractOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(2.2);
+end;
+
+procedure TTestParserExpressions.TestSimpleSubtractIntegerFloat;
+begin
+  FP.Expression:='3-1.2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPSubtractOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TIntToFloatNode,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(1.8);
+end;
+
+procedure TTestParserExpressions.TestSimpleSubtractFloatInteger;
+begin
+  FP.Expression:='3.3-2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPSubtractOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TIntToFloatNode);
+  AssertResultType(rtFloat);
+  AssertResult(1.3);
+end;
+
+procedure TTestParserExpressions.TestSimpleMultiplyInteger;
+begin
+  FP.Expression:='4*2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(8);
+end;
+
+procedure TTestParserExpressions.TestSimpleMultiplyFloat;
+begin
+  FP.Expression:='3.4*1.5';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(5.1);
+end;
+
+procedure TTestParserExpressions.TestSimpleDivideInteger;
+begin
+  FP.Expression:='4/2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPDivideOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(2.0);
+end;
+
+procedure TTestParserExpressions.TestSimpleDivideFloat;
+begin
+  FP.Expression:='5.1/1.5';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPDivideOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(3.4);
+end;
+
+procedure TTestParserExpressions.TestSimpleBooleanAnd;
+begin
+  FP.Expression:='true and true';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserExpressions.TestSimpleIntegerAnd;
+begin
+  FP.Expression:='3 and 1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(1);
+end;
+
+procedure TTestParserExpressions.TestSimpleBooleanOr;
+begin
+  FP.Expression:='false or true';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserExpressions.TestSimpleIntegerOr;
+begin
+  FP.Expression:='2 or 1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(3);
+end;
+
+procedure TTestParserExpressions.TestSimpleBooleanNot;
+begin
+  FP.Expression:='not false';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Not node',TFPNotNode, FP.ExprNode);
+  AssertOperand(FP.ExprNode,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(true);
+end;
+
+procedure TTestParserExpressions.TestSimpleIntegerNot;
+begin
+  FP.Expression:='Not 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Not node',TFPNotNode, FP.ExprNode);
+  AssertOperand(FP.ExprNode,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(Not Int64(3));
+end;
+
+procedure TTestParserExpressions.TestSimpleAddSeries;
+begin
+  FP.Expression:='1 + 2 + 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPAddOperation,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(6);
+end;
+
+procedure TTestParserExpressions.TestSimpleMultiplySeries;
+begin
+  FP.Expression:='2 * 3 * 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPMultiplyOperation,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(24);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddMultiplySeries;
+begin
+  FP.Expression:='2 * 3 + 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPMultiplyOperation,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(10);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddAndSeries;
+begin
+  // 2 and (3+4)
+  FP.Expression:='2 and 3 + 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPAddOperation);
+  AssertResultType(rtInteger);
+  AssertResult(2);
+end;
+
+procedure TTestParserExpressions.TestSimpleAddOrSeries;
+begin
+  // 2 or (3+4)
+  FP.Expression:='2 or 3 + 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPAddOperation);
+  AssertResultType(rtInteger);
+  AssertResult(7);
+end;
+
+procedure TTestParserExpressions.TestSimpleOrNotSeries;
+begin
+  FP.Expression:='Not 1 or 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPNotNode,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult((Not Int64(1)) or Int64(3));
+end;
+
+procedure TTestParserExpressions.TestSimpleAndNotSeries;
+begin
+  FP.Expression:='Not False and False';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPNotNode,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserExpressions.TestDoubleAddMultiplySeries;
+begin
+  FP.Expression:='2 * 3 + 4 * 5';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPMultiplyOperation,TFPMultiplyOperation);
+  AssertResultType(rtInteger);
+  AssertResult(26);
+end;
+
+procedure TTestParserExpressions.TestDoubleSubtractMultiplySeries;
+begin
+  FP.Expression:='4 * 5 - 2 * 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPSubtractOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPMultiplyOperation,TFPMultiplyOperation);
+  AssertResultType(rtInteger);
+  AssertResult(14);
+end;
+
+procedure TTestParserExpressions.TestSimpleIfInteger;
+begin
+  FP.Expression:='If(True,1,2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('If operation',TIfOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(1);
+end;
+
+procedure TTestParserExpressions.TestSimpleIfString;
+begin
+  FP.Expression:='If(True,''a'',''b'')';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('If operation',TIfOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtString);
+  AssertResult('a');
+end;
+
+procedure TTestParserExpressions.TestSimpleIfFloat;
+begin
+  FP.Expression:='If(True,1.2,3.4)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('If operation',TIfOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtFloat);
+  AssertResult(1.2);
+end;
+
+procedure TTestParserExpressions.TestSimpleIfBoolean;
+begin
+  FP.Expression:='If(True,False,True)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('If operation',TIfOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserExpressions.TestSimpleIfDateTime;
+begin
+  FP.Identifiers.AddDateTimeVariable('a',Date);
+  FP.Identifiers.AddDateTimeVariable('b',Date-1);
+  FP.Expression:='If(True,a,b)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('If operation',TIfOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPExprVariable,TFPExprVariable);
+  AssertResultType(rtDateTime);
+  AssertResult(Date);
+end;
+
+procedure TTestParserExpressions.TestSimpleIfOperation;
+begin
+  FP.Expression:='If(True,''a'',''b'')+''c''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertResultType(rtString);
+  AssertResult('ac');
+end;
+
+procedure TTestParserExpressions.TestSimpleBrackets;
+begin
+  FP.Expression:='(4 + 2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPAddOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(6);
+end;
+
+procedure TTestParserExpressions.TestSimpleBrackets2;
+begin
+  FP.Expression:='(4 * 2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(8);
+end;
+
+procedure TTestParserExpressions.TestSimpleBracketsLeft;
+begin
+  FP.Expression:='(4 + 2) * 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPAddOperation,TFPConstExpression);
+  AssertResultType(rtInteger);
+  AssertResult(18);
+end;
+
+procedure TTestParserExpressions.TestSimpleBracketsRight;
+begin
+  FP.Expression:='3 * (4 + 2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPAddOperation);
+  AssertResultType(rtInteger);
+  AssertResult(18);
+end;
+
+procedure TTestParserExpressions.TestSimpleBracketsDouble;
+begin
+  FP.Expression:='(3 + 4) * (4 + 2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPMultiplyOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPAddOperation,TFPAddOperation);
+  AssertResultType(rtInteger);
+  AssertResult(42);
+end;
+
+//TTestParserBooleanOperations
+
+procedure TTestParserBooleanOperations.TestEqualInteger;
+begin
+  FP.Expression:='1 = 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestUnEqualInteger;
+begin
+  FP.Expression:='1 <> 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPUnEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestEqualFloat;
+begin
+  FP.Expression:='1.2 = 2.3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestEqualFloat2;
+begin
+  FP.Expression:='1.2 = 1.2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestUnEqualFloat;
+begin
+  FP.Expression:='1.2 <> 2.3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPUnEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+procedure TTestParserBooleanOperations.TestEqualString;
+begin
+  FP.Expression:='''1.2'' = ''2.3''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestEqualString2;
+begin
+  FP.Expression:='''1.2'' = ''1.2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestUnEqualString;
+begin
+  FP.Expression:='''1.2'' <> ''2.3''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPUnEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestUnEqualString2;
+begin
+  FP.Expression:='''aa'' <> ''AA''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPUnEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestEqualBoolean;
+begin
+  FP.Expression:='False = True';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestUnEqualBoolean;
+begin
+  FP.Expression:='False <> True';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPUnEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanInteger;
+begin
+  FP.Expression:='1 < 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanInteger2;
+begin
+  FP.Expression:='2 < 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanEqualInteger;
+begin
+  FP.Expression:='3 <= 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanEqualInteger2;
+begin
+  FP.Expression:='2 <= 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanFloat;
+begin
+  FP.Expression:='1.2 < 2.3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanFloat2;
+begin
+  FP.Expression:='2.2 < 2.2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanEqualFloat;
+begin
+  FP.Expression:='3.1 <= 2.1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanEqualFloat2;
+begin
+  FP.Expression:='2.1 <= 2.1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanString;
+begin
+  FP.Expression:='''1'' < ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanString2;
+begin
+  FP.Expression:='''2'' < ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanEqualString;
+begin
+  FP.Expression:='''3'' <= ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestLessThanEqualString2;
+begin
+  FP.Expression:='''2'' <= ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPLessThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+
+procedure TTestParserBooleanOperations.TestGreaterThanInteger;
+begin
+  FP.Expression:='1 > 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanInteger2;
+begin
+  FP.Expression:='2 > 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanEqualInteger;
+begin
+  FP.Expression:='3 >= 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanEqualInteger2;
+begin
+  FP.Expression:='2 >= 2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanFloat;
+begin
+  FP.Expression:='1.2 > 2.3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanFloat2;
+begin
+  FP.Expression:='2.2 > 2.2';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanEqualFloat;
+begin
+  FP.Expression:='3.1 >= 2.1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanEqualFloat2;
+begin
+  FP.Expression:='2.1 >= 2.1';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanString;
+begin
+  FP.Expression:='''1'' > ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanString2;
+begin
+  FP.Expression:='''2'' > ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanEqualString;
+begin
+  FP.Expression:='''3'' >= ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.TestGreaterThanEqualString2;
+begin
+  FP.Expression:='''2'' >= ''2''';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPGreaterThanEqualOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPConstExpression,TFPConstExpression);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.EqualAndSeries;
+begin
+  // (1=2) and (3=4)
+  FP.Expression:='1 = 2 and 3 = 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPEqualOperation,TFPEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.EqualAndSeries2;
+begin
+  // (1=2) and (3=4)
+  FP.Expression:='1 = 1 and 3 = 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPEqualOperation,TFPEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.EqualOrSeries;
+begin
+  // (1=2) or (3=4)
+  FP.Expression:='1 = 2 or 3 = 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPEqualOperation,TFPEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.EqualOrSeries2;
+begin
+  // (1=1) or (3=4)
+  FP.Expression:='1 = 1 or 3 = 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPEqualOperation,TFPEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.UnEqualAndSeries;
+begin
+  // (1<>2) and (3<>4)
+  FP.Expression:='1 <> 2 and 3 <> 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPUnEqualOperation,TFPUnEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.UnEqualAndSeries2;
+begin
+  // (1<>2) and (3<>4)
+  FP.Expression:='1 <> 1 and 3 <> 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPUnEqualOperation,TFPUnEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.UnEqualOrSeries;
+begin
+  // (1<>2) or (3<>4)
+  FP.Expression:='1 <> 2 or 3 <> 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPUnEqualOperation,TFPUnEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.UnEqualOrSeries2;
+begin
+  // (1<>1) or (3<>4)
+  FP.Expression:='1 <> 1 or 3 <> 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPUnEqualOperation,TFPUnEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.LessThanAndSeries;
+begin
+  // (1<2) and (3<4)
+  FP.Expression:='1 < 2 and 3 < 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanOperation,TFPLessThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.LessThanAndSeries2;
+begin
+  // (1<2) and (3<4)
+  FP.Expression:='1 < 1 and 3 < 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanOperation,TFPLessThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.LessThanOrSeries;
+begin
+  // (1<2) or (3<4)
+  FP.Expression:='1 < 2 or 3 < 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanOperation,TFPLessThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.LessThanOrSeries2;
+begin
+  // (1<1) or (3<4)
+  FP.Expression:='1 < 1 or 3 < 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanOperation,TFPLessThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanAndSeries;
+begin
+  // (1>2) and (3>4)
+  FP.Expression:='1 > 2 and 3 > 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanOperation,TFPGreaterThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanAndSeries2;
+begin
+  // (1>2) and (3>4)
+  FP.Expression:='1 > 1 and 3 > 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanOperation,TFPGreaterThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanOrSeries;
+begin
+  // (1>2) or (3>4)
+  FP.Expression:='1 > 2 or 3 > 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanOperation,TFPGreaterThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanOrSeries2;
+begin
+  // (1>1) or (3>4)
+  FP.Expression:='1 > 1 or 3 > 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanOperation,TFPGreaterThanOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.LessThanEqualAndSeries;
+begin
+  // (1<=2) and (3<=4)
+  FP.Expression:='1 <= 2 and 3 <= 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanEqualOperation,TFPLessThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.LessThanEqualAndSeries2;
+begin
+  // (1<=2) and (3<=4)
+  FP.Expression:='1 <= 1 and 3 <= 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanEqualOperation,TFPLessThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.LessThanEqualOrSeries;
+begin
+  // (1<=2) or (3<=4)
+  FP.Expression:='1 <= 2 or 3 <= 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanEqualOperation,TFPLessThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.LessThanEqualOrSeries2;
+begin
+  // (1<=1) or (3<=4)
+  FP.Expression:='1 <= 1 or 3 <= 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPLessThanEqualOperation,TFPLessThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanEqualAndSeries;
+begin
+  // (1>=2) and (3>=4)
+  FP.Expression:='1 >= 2 and 3 >= 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanEqualOperation,TFPGreaterThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanEqualAndSeries2;
+begin
+  // (1>=2) and (3>=4)
+  FP.Expression:='1 >= 1 and 3 >= 3';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryAndOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanEqualOperation,TFPGreaterThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanEqualOrSeries;
+begin
+  // (1>=2) or (3>=4)
+  FP.Expression:='1 >= 2 or 3 >= 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanEqualOperation,TFPGreaterThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(False);
+end;
+
+procedure TTestParserBooleanOperations.GreaterThanEqualOrSeries2;
+begin
+  // (1>=1) or (3>=4)
+  FP.Expression:='1 >= 1 or 3 >= 4';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPBinaryOrOperation, FP.ExprNode);
+  AssertLeftRight(FP.ExprNode,TFPGreaterThanEqualOperation,TFPGreaterThanEqualOperation);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+//TTestParserOperands
+procedure TTestParserOperands.MissingOperand1;
+begin
+  TestParser('1+');
+end;
+
+procedure TTestParserOperands.MissingOperand2;
+begin
+  TestParser('*1');
+end;
+
+procedure TTestParserOperands.MissingOperand3;
+begin
+  TestParser('1*');
+end;
+
+procedure TTestParserOperands.MissingOperand4;
+begin
+  TestParser('1+');
+end;
+
+procedure TTestParserOperands.MissingOperand5;
+begin
+  TestParser('1 and');
+end;
+
+procedure TTestParserOperands.MissingOperand6;
+begin
+  TestParser('1 or');
+end;
+
+procedure TTestParserOperands.MissingOperand7;
+begin
+  TestParser('and 1');
+end;
+
+procedure TTestParserOperands.MissingOperand8;
+begin
+  TestParser('or 1');
+end;
+
+procedure TTestParserOperands.MissingOperand9;
+begin
+  TestParser('1-');
+end;
+
+procedure TTestParserOperands.MissingOperand10;
+begin
+  TestParser('1 = ');
+end;
+
+procedure TTestParserOperands.MissingOperand11;
+begin
+  TestParser('= 1');
+end;
+
+procedure TTestParserOperands.MissingOperand12;
+begin
+  TestParser('1 <> ');
+end;
+
+procedure TTestParserOperands.MissingOperand13;
+begin
+  TestParser('<> 1');
+end;
+
+procedure TTestParserOperands.MissingOperand14;
+begin
+  TestParser('1 >= ');
+end;
+
+procedure TTestParserOperands.MissingOperand15;
+begin
+  TestParser('>= 1');
+end;
+
+procedure TTestParserOperands.MissingOperand16;
+begin
+  TestParser('1 <= ');
+end;
+
+procedure TTestParserOperands.MissingOperand17;
+begin
+  TestParser('<= 1');
+end;
+
+procedure TTestParserOperands.MissingOperand18;
+begin
+  TestParser('1 < ');
+end;
+
+procedure TTestParserOperands.MissingOperand19;
+begin
+  TestParser('< 1');
+end;
+
+procedure TTestParserOperands.MissingOperand20;
+begin
+  TestParser('1 > ');
+end;
+
+procedure TTestParserOperands.MissingOperand21;
+begin
+  TestParser('> 1');
+end;
+
+procedure TTestParserOperands.MissingBracket1;
+begin
+  TestParser('(1+3');
+end;
+
+procedure TTestParserOperands.MissingBracket2;
+begin
+  TestParser('1+3)');
+end;
+
+procedure TTestParserOperands.MissingBracket3;
+begin
+  TestParser('(1+3))');
+end;
+
+procedure TTestParserOperands.MissingBracket4;
+begin
+  TestParser('((1+3)');
+end;
+
+procedure TTestParserOperands.MissingBracket5;
+begin
+  TestParser('((1+3) 4');
+end;
+
+procedure TTestParserOperands.MissingBracket6;
+begin
+  TestParser('IF(true,1,2');
+end;
+
+procedure TTestParserOperands.MissingBracket7;
+begin
+  TestParser('case(1,1,2,4');
+end;
+
+procedure TTestParserOperands.MissingArgument1;
+begin
+  TestParser('IF(true,1)');
+end;
+
+procedure TTestParserOperands.MissingArgument2;
+begin
+  TestParser('IF(True)');
+end;
+
+procedure TTestParserOperands.MissingArgument3;
+begin
+  TestParser('case(1)');
+end;
+
+procedure TTestParserOperands.MissingArgument4;
+begin
+  TestParser('case(1,2)');
+end;
+
+procedure TTestParserOperands.MissingArgument5;
+
+begin
+  TestParser('case(1,2,3)');
+end;
+
+procedure TTestParserOperands.MissingArgument6;
+
+begin
+  TestParser('IF(true,1,2,3)');
+end;
+
+procedure TTestParserOperands.MissingArgument7;
+
+begin
+  TestParser('case(0,1,2,3,4,5,6)');
+end;
+
+procedure TTestParserTypeMatch.AccessString;
+begin
+  FP.AsString;
+end;
+
+procedure TTestParserTypeMatch.AccessInteger;
+begin
+  FP.AsInteger;
+end;
+
+procedure TTestParserTypeMatch.AccessFloat;
+begin
+  FP.AsFloat;
+end;
+
+procedure TTestParserTypeMatch.AccessDateTime;
+begin
+  FP.AsDateTime;
+end;
+
+procedure TTestParserTypeMatch.AccessBoolean;
+begin
+  FP.AsBoolean;
+end;
+
+//TTestParserTypeMatch
+procedure TTestParserTypeMatch.TestTypeMismatch1;
+begin
+  TestParser('1+''string''');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch2;
+begin
+  TestParser('1+True');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch3;
+begin
+  TestParser('True+''string''');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch4;
+begin
+  TestParser('1.23+''string''');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch5;
+begin
+  TestParser('1.23+true');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch6;
+begin
+  TestParser('1.23 and true');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch7;
+begin
+  TestParser('1.23 or true');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch8;
+begin
+  TestParser('''string'' or true');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch9;
+begin
+  TestParser('''string'' and true');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch10;
+begin
+  TestParser('1.23 or 1');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch11;
+begin
+  TestParser('1.23 and 1');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch12;
+begin
+  TestParser('''astring'' = 1');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch13;
+begin
+  TestParser('true = 1');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch14;
+begin
+  TestParser('true * 1');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch15;
+begin
+  TestParser('''astring'' * 1');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch16;
+begin
+  TestParser('If(1,1,1)');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch17;
+begin
+  TestParser('If(True,1,''3'')');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch18;
+begin
+  TestParser('case(1,1,''3'',1)');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch19;
+begin
+  TestParser('case(1,1,1,''3'')');
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch20;
+begin
+  FP.Expression:='1';
+  AssertException('Accessing integer as string',EExprParser,@AccessString);
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch21;
+begin
+  FP.Expression:='''a''';
+  AssertException('Accessing string as integer',EExprParser,@AccessInteger);
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch22;
+begin
+  FP.Expression:='''a''';
+  AssertException('Accessing string as float',EExprParser,@AccessFloat);
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch23;
+begin
+  FP.Expression:='''a''';
+  AssertException('Accessing string as boolean',EExprParser,@AccessBoolean);
+end;
+
+procedure TTestParserTypeMatch.TestTypeMismatch24;
+begin
+  FP.Expression:='''a''';
+  AssertException('Accessing string as datetime',EExprParser,@AccessDateTime);
+end;
+
+//TTestParserVariables
+
+Procedure GetDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=Date;
+end;
+
+procedure TTestParserVariables.TestVariable1;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddVariable('a',rtBoolean,'True');
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Variable has correct resulttype',rtBoolean,I.ResultType);
+  AssertEquals('Variable has correct value','True',I.Value);
+end;
+
+procedure TTestParserVariables.TestVariable2;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddBooleanVariable('a',False);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Variable has correct resulttype',rtBoolean,I.ResultType);
+  AssertEquals('Variable has correct value','False',I.Value);
+end;
+
+procedure TTestParserVariables.TestVariable3;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',123);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Variable has correct resulttype',rtInteger,I.ResultType);
+  AssertEquals('Variable has correct value','123',I.Value);
+end;
+
+procedure TTestParserVariables.TestVariable4;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFloatVariable('a',1.23);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Variable has correct resulttype',rtFloat,I.ResultType);
+  AssertEquals('Variable has correct value',FloatToStr(1.23),I.Value);
+end;
+
+procedure TTestParserVariables.TestVariable5;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddStringVariable('a','1.23');
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Variable has correct resulttype',rtString,I.ResultType);
+  AssertEquals('Variable has correct value','1.23',I.Value);
+end;
+
+procedure TTestParserVariables.TestVariable6;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Now;
+  I:=FP.Identifiers.AddDateTimeVariable('a',D);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Variable has correct resulttype',rtDateTime,I.ResultType);
+  AssertEquals('Variable has correct value',FormatDateTime('cccc',D),I.Value);
+end;
+
+procedure TTestParserVariables.AddVariabletwice;
+
+begin
+  FP.Identifiers.AddDateTimeVariable('a',Now);
+end;
+
+procedure TTestParserVariables.UnknownVariable;
+begin
+  FP.Identifiers.IdentifierByName('unknown');
+end;
+
+procedure TTestParserVariables.ReadWrongType;
+
+Var
+  Res : TFPExpressioNResult;
+
+begin
+  AssertEquals('Only one identifier',1,FP.Identifiers.Count);
+  Case FAsWrongType of
+    rtBoolean  : res.ResBoolean:=FP.Identifiers[0].AsBoolean;
+    rtString   : res.ResString:=FP.Identifiers[0].AsString;
+    rtInteger  : Res.ResInteger:=FP.Identifiers[0].AsInteger;
+    rtFloat    : Res.ResFloat:=FP.Identifiers[0].AsFloat;
+    rtDateTime : Res.ResDateTime:=FP.Identifiers[0].AsDateTime;
+  end;
+end;
+
+procedure TTestParserVariables.WriteWrongType;
+
+Var
+  Res : TFPExpressioNResult;
+
+begin
+  AssertEquals('Only one identifier',1,FP.Identifiers.Count);
+  Case FAsWrongType of
+    rtBoolean  : FP.Identifiers[0].AsBoolean:=res.ResBoolean;
+    rtString   : FP.Identifiers[0].AsString:=res.ResString;
+    rtInteger  : FP.Identifiers[0].AsInteger:=Res.ResInteger;
+    rtFloat    : FP.Identifiers[0].AsFloat:=Res.ResFloat;
+    rtDateTime : FP.Identifiers[0].AsDateTime:=Res.ResDateTime;
+  end;
+end;
+
+procedure TTestParserVariables.DoDummy(var Result: TFPExpressionResult;
+  const Args: TExprParameterArray);
+begin
+  // Do nothing;
+end;
+
+procedure TTestParserVariables.TestVariableAssign;
+
+Var
+  I,J : TFPExprIdentifierDef;
+
+begin
+  I:=TFPExprIdentifierDef.Create(Nil);
+  try
+    J:=TFPExprIdentifierDef.Create(Nil);
+    try
+      I.Name:='Aname';
+      I.ParameterTypes:='ISDBF';
+      I.ResultType:=rtFloat;
+      I.Value:='1.23';
+      I.OnGetFunctionValue:=@DoDummy;
+      I.OnGetFunctionValueCallBack:=@GetDate;
+      J.Assign(I);
+      AssertEquals('Names match',I.Name,J.Name);
+      AssertEquals('Parametertypes match',I.ParameterTypes,J.ParameterTypes);
+      AssertEquals('Values match',I.Value,J.Value);
+      AssertEquals('Result types match',Ord(I.ResultType),Ord(J.ResultType));
+      AssertSame('Callbacks match',Pointer(I.OnGetFunctionValueCallBack),Pointer(J.OnGetFunctionValueCallback));
+      If (I.OnGetFunctionValue)<>(J.OnGetFunctionValue) then
+        Fail('OnGetFUnctionValue as Method does not match');
+    finally
+      J.Free;
+    end;
+  finally
+    I.Free;
+  end;
+end;
+
+procedure TTestParserVariables.TestVariableAssignAgain;
+
+Var
+  I,J : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=TFPBuiltinExprIdentifierDef.Create(Nil);
+  try
+    J:=TFPBuiltinExprIdentifierDef.Create(Nil);
+    try
+      I.Name:='Aname';
+      I.ParameterTypes:='ISDBF';
+      I.ResultType:=rtFloat;
+      I.Value:='1.23';
+      I.OnGetFunctionValue:=@DoDummy;
+      I.OnGetFunctionValueCallBack:=@GetDate;
+      I.Category:=bcUser;
+      J.Assign(I);
+      AssertEquals('Names match',I.Name,J.Name);
+      AssertEquals('Parametertypes match',I.ParameterTypes,J.ParameterTypes);
+      AssertEquals('Values match',I.Value,J.Value);
+      AssertEquals('Result types match',Ord(I.ResultType),Ord(J.ResultType));
+      AssertEquals('Categories match',Ord(I.Category),Ord(J.Category));
+      AssertSame('Callbacks match',Pointer(I.OnGetFunctionValueCallBack),Pointer(J.OnGetFunctionValueCallback));
+      If (I.OnGetFunctionValue)<>(J.OnGetFunctionValue) then
+        Fail('OnGetFUnctionValue as Method does not match');
+    finally
+      J.Free;
+    end;
+  finally
+    I.Free;
+  end;
+end;
+
+procedure TTestParserVariables.TestVariable7;
+
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Now;
+  I:=FP.Identifiers.AddDateTimeVariable('a',D);
+  AssertException('Cannot add same name twice',EExprParser,@AddVariabletwice);
+end;
+
+procedure TTestParserVariables.TestVariable8;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  FP.Identifiers.AddIntegerVariable('a',123);
+  FP.Identifiers.AddIntegerVariable('b',123);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  FP.BuildHashList;
+  FP.Identifiers.Delete(0);
+  AssertEquals('List is dirty',True,FP.Dirty);
+end;
+
+procedure TTestParserVariables.TestVariable9;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',123);
+  FP.Expression:='a';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPExprVariable, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(123);
+end;
+
+procedure TTestParserVariables.TestVariable10;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddStringVariable('a','a123');
+  FP.Expression:='a';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPExprVariable, FP.ExprNode);
+  AssertResultType(rtString);
+  AssertResult('a123');
+end;
+
+procedure TTestParserVariables.TestVariable11;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFloatVariable('a',1.23);
+  FP.Expression:='a';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPExprVariable, FP.ExprNode);
+  AssertResultType(rtFloat);
+  AssertResult(1.23);
+end;
+
+procedure TTestParserVariables.TestVariable12;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddBooleanVariable('a',True);
+  FP.Expression:='a';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPExprVariable, FP.ExprNode);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserVariables.TestVariable13;
+
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddDateTimeVariable('a',D);
+  FP.Expression:='a';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPExprVariable, FP.ExprNode);
+  AssertResultType(rtDateTime);
+  AssertDateTimeResult(D);
+end;
+
+procedure TTestParserVariables.TestVariable14;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  FP.BuildHashList;
+  S:=FP.IdentifierByName('a');
+  AssertSame('Identifier found',I,S);
+end;
+
+procedure TTestParserVariables.TestVariable15;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  FP.BuildHashList;
+  S:=FP.IdentifierByName('A');
+  AssertSame('Identifier found',I,S);
+end;
+
+procedure TTestParserVariables.TestVariable16;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  FP.BuildHashList;
+  S:=FP.IdentifierByName('B');
+  AssertNull('Identifier not found',S);
+end;
+
+procedure TTestParserVariables.TestVariable17;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  FP.BuildHashList;
+  AssertException('Identifier not found',EExprParser,@unknownvariable);
+end;
+
+procedure TTestParserVariables.TestVariable18;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  S:=FP.Identifiers.FindIdentifier('B');
+  AssertNull('Identifier not found',S);
+end;
+
+procedure TTestParserVariables.TestVariable19;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  S:=FP.Identifiers.FindIdentifier('a');
+  AssertSame('Identifier found',I,S);
+end;
+
+procedure TTestParserVariables.TestVariable20;
+
+Var
+  I,S : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddIntegerVariable('a',1);
+  S:=FP.Identifiers.FindIdentifier('A');
+  AssertSame('Identifier found',I,S);
+end;
+
+procedure TTestParserVariables.TestAccess(Skip : TResultType);
+
+Var
+  rt : TResultType;
+
+begin
+  For rt:=Low(TResultType) to High(TResultType) do
+    if rt<>skip then
+      begin
+      FasWrongType:=rt;
+      AssertException('Acces as '+ResultTypeName(rt),EExprParser,@ReadWrongtype);
+      end;
+  For rt:=Low(TResultType) to High(TResultType) do
+    if rt<>skip then
+      begin
+      FasWrongType:=rt;
+      AssertException('Acces as '+ResultTypeName(rt),EExprParser,@WriteWrongtype);
+      end;
+end;
+
+procedure TTestParserVariables.TestVariable21;
+begin
+  FP.IDentifiers.AddIntegerVariable('a',1);
+  TestAccess(rtInteger);
+end;
+
+procedure TTestParserVariables.TestVariable22;
+begin
+  FP.IDentifiers.AddFloatVariable('a',1.0);
+  TestAccess(rtFloat);
+end;
+
+procedure TTestParserVariables.TestVariable23;
+begin
+  FP.IDentifiers.AddStringVariable('a','1.0');
+  TestAccess(rtString);
+end;
+
+procedure TTestParserVariables.TestVariable24;
+begin
+  FP.IDentifiers.AddBooleanVariable('a',True);
+  TestAccess(rtBoolean);
+end;
+
+procedure TTestParserVariables.TestVariable25;
+
+begin
+  FP.IDentifiers.AddDateTimeVariable('a',Date);
+  TestAccess(rtDateTime);
+end;
+
+procedure TTestParserVariables.TestVariable26;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.IDentifiers.AddStringVariable('a','1.0');
+  I.AsString:='12';
+  AssertEquals('Correct value','12',I.AsString);
+end;
+
+procedure TTestParserVariables.TestVariable27;
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.IDentifiers.AddIntegerVariable('a',10);
+  I.Asinteger:=12;
+  AssertEquals('Correct value',12,I.AsInteger);
+end;
+
+procedure TTestParserVariables.TestVariable28;
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.IDentifiers.AddFloatVariable('a',1.0);
+  I.AsFloat:=1.2;
+  AssertEquals('Correct value',1.2,I.AsFloat);
+end;
+
+procedure TTestParserVariables.TestVariable29;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.IDentifiers.AddDateTimeVariable('a',Now);
+  I.AsDateTime:=Date-1;
+  AssertEquals('Correct value',Date-1,I.AsDateTime);
+end;
+
+procedure TTestParserVariables.TestVariable30;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddBooleanVariable('a',True);
+  I.AsBoolean:=False;
+  AssertEquals('Correct value',False,I.AsBoolean);
+end;
+
+
+
+Procedure EchoDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=Args[0].resDateTime;
+end;
+
+Procedure EchoInteger(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=Args[0].resInteger;
+end;
+
+Procedure EchoBoolean(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resBoolean:=Args[0].resBoolean;
+end;
+
+Procedure EchoFloat(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resFloat:=Args[0].resFloat;
+end;
+
+Procedure EchoString(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=Args[0].resString;
+end;
+
+Procedure TTestExpressionParser.DoEchoDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=Args[0].resDateTime;
+end;
+
+Procedure TTestExpressionParser.DoEchoInteger(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=Args[0].resInteger;
+end;
+
+Procedure TTestExpressionParser.DoEchoBoolean(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resBoolean:=Args[0].resBoolean;
+end;
+
+Procedure TTestExpressionParser.DoEchoFloat(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resFloat:=Args[0].resFloat;
+end;
+
+Procedure TTestExpressionParser.DoEchoString(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=Args[0].resString;
+end;
+
+procedure TTestExpressionParser.DoGetDate(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+begin
+  Result.ResDatetime:=Date;
+end;
+
+procedure TTestExpressionParser.DoAddInteger(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+begin
+  Result.Resinteger:=Args[0].ResInteger+Args[1].ResInteger;
+end;
+
+procedure TTestExpressionParser.DoDeleteString(var Result: TFPExpressionResult; Const Args: TExprParameterArray);
+begin
+  Result.ResString:=Args[0].ResString;
+  Delete(Result.ResString,Args[1].ResInteger,Args[2].ResInteger);
+end;
+
+procedure TTestParserFunctions.TryRead;
+
+Var
+  Res : TFPExpressioNResult;
+
+begin
+  AssertEquals('Only one identifier',1,FP.Identifiers.Count);
+  Case FAccessAs of
+    rtBoolean  : res.ResBoolean:=FP.Identifiers[0].AsBoolean;
+    rtString   : res.ResString:=FP.Identifiers[0].AsString;
+    rtInteger  : Res.ResInteger:=FP.Identifiers[0].AsInteger;
+    rtFloat    : Res.ResFloat:=FP.Identifiers[0].AsFloat;
+    rtDateTime : Res.ResDateTime:=FP.Identifiers[0].AsDateTime;
+  end;
+end;
+
+procedure TTestParserFunctions.TryWrite;
+
+Var
+  Res : TFPExpressioNResult;
+
+begin
+  AssertEquals('Only one identifier',1,FP.Identifiers.Count);
+  Case FAccessAs of
+    rtBoolean  : FP.Identifiers[0].AsBoolean:=res.ResBoolean;
+    rtString   : FP.Identifiers[0].AsString:=res.ResString;
+    rtInteger  : FP.Identifiers[0].AsInteger:=Res.ResInteger;
+    rtFloat    : FP.Identifiers[0].AsFloat:=Res.ResFloat;
+    rtDateTime : FP.Identifiers[0].AsDateTime:=Res.ResDateTime;
+  end;
+end;
+
+// TTestParserFunctions
+procedure TTestParserFunctions.TestFunction1;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('Date','D','',@GetDate);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtDateTime,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@GetDate),Pointer(I.OnGetFunctionValueCallBack));
+  FaccessAs:=rtDateTime;
+  AssertException('No read access',EExprParser,@TryRead);
+  AssertException('No write access',EExprParser,@TryWrite);
+end;
+
+procedure TTestParserFunctions.TestFunction2;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoDate','D','D',@EchoDate);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtDateTime,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@EchoDate),Pointer(I.OnGetFunctionValueCallBack));
+end;
+
+procedure TTestParserFunctions.TestFunction3;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoInteger','I','I',@EchoInteger);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtInteger,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@EchoInteger),Pointer(I.OnGetFunctionValueCallBack));
+  FaccessAs:=rtInteger;
+  AssertException('No read access',EExprParser,@TryRead);
+  AssertException('No write access',EExprParser,@TryWrite);
+end;
+
+procedure TTestParserFunctions.TestFunction4;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoBoolean','B','B',@EchoBoolean);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtBoolean,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@EchoBoolean),Pointer(I.OnGetFunctionValueCallBack));
+  FaccessAs:=rtBoolean;
+  AssertException('No read access',EExprParser,@TryRead);
+  AssertException('No write access',EExprParser,@TryWrite);
+end;
+
+procedure TTestParserFunctions.TestFunction5;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoFloat','F','F',@EchoFloat);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtFloat,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@EchoFloat),Pointer(I.OnGetFunctionValueCallBack));
+  FaccessAs:=rtfloat;
+  AssertException('No read access',EExprParser,@TryRead);
+  AssertException('No write access',EExprParser,@TryWrite);
+end;
+
+procedure TTestParserFunctions.TestFunction6;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoString','S','S',@EchoString);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtString,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@EchoString),Pointer(I.OnGetFunctionValueCallBack));
+  FaccessAs:=rtString;
+  AssertException('No read access',EExprParser,@TryRead);
+  AssertException('No write access',EExprParser,@TryWrite);
+end;
+
+procedure TTestParserFunctions.TestFunction7;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoDate','D','D',@DoEchoDate);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtDateTime,I.ResultType);
+//  AssertSame('Function has correct address',TMethod(@Self.DoEchoDate),TMethod(I.OnGetFunctionValue));
+end;
+
+procedure TTestParserFunctions.TestFunction8;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoInteger','I','I',@DOEchoInteger);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtInteger,I.ResultType);
+//  AssertSame('Function has correct address',Pointer(@EchoInteger),Pointer(I.OnGetFunctionValueCallBack));
+end;
+
+procedure TTestParserFunctions.TestFunction9;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoBoolean','B','B',@DoEchoBoolean);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtBoolean,I.ResultType);
+//  AssertSame('Function has correct address',Pointer(@EchoBoolean),Pointer(I.OnGetFunctionValueCallBack));
+end;
+
+procedure TTestParserFunctions.TestFunction10;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoFloat','F','F',@DoEchoFloat);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtFloat,I.ResultType);
+//  AssertSame('Function has correct address',Pointer(@EchoFloat),Pointer(I.OnGetFunctionValueCallBack));
+end;
+
+procedure TTestParserFunctions.TestFunction11;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('EchoString','S','S',@DoEchoString);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtString,I.ResultType);
+//  AssertSame('Function has correct address',Pointer(@EchoString),Pointer(I.OnGetFunctionValueCallBack));
+end;
+
+procedure TTestParserFunctions.TestFunction12;
+
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('Date','D','',@GetDate);
+  FP.Expression:='Date';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionCallBack, FP.ExprNode);
+  AssertResultType(rtDateTime);
+  AssertDateTimeResult(D);
+end;
+
+procedure TTestParserFunctions.TestFunction13;
+
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddDateTimeVariable('a',D);
+  I:=FP.Identifiers.AddFunction('EchoDate','D','D',@EchoDate);
+  FP.Expression:='EchoDate(a)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionCallBack, FP.ExprNode);
+  AssertResultType(rtDateTime);
+  AssertDateTimeResult(D);
+end;
+
+procedure TTestParserFunctions.TestFunction14;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoInteger','I','I',@EchoInteger);
+  FP.Expression:='EchoInteger(13)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionCallBack, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(13);
+end;
+
+procedure TTestParserFunctions.TestFunction15;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoBoolean','B','B',@EchoBoolean);
+  FP.Expression:='EchoBoolean(True)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionCallBack, FP.ExprNode);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserFunctions.TestFunction16;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoFloat','F','F',@EchoFloat);
+  FP.Expression:='EchoFloat(1.234)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionCallBack, FP.ExprNode);
+  AssertResultType(rtFloat);
+  AssertResult(1.234);
+end;
+
+procedure TTestParserFunctions.TestFunction17;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoString','S','S',@EchoString);
+  FP.Expression:='EchoString(''Aloha'')';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionCallBack, FP.ExprNode);
+  AssertResultType(rtString);
+  AssertResult('Aloha');
+end;
+
+
+procedure TTestParserFunctions.TestFunction18;
+
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddDateTimeVariable('a',D);
+  I:=FP.Identifiers.AddFunction('EchoDate','D','D',@DoEchoDate);
+  FP.Expression:='EchoDate(a)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtDateTime);
+  AssertDateTimeResult(D);
+end;
+
+procedure TTestParserFunctions.TestFunction19;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoInteger','I','I',@DoEchoInteger);
+  FP.Expression:='EchoInteger(13)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(13);
+end;
+
+procedure TTestParserFunctions.TestFunction20;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoBoolean','B','B',@DoEchoBoolean);
+  FP.Expression:='EchoBoolean(True)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtBoolean);
+  AssertResult(True);
+end;
+
+procedure TTestParserFunctions.TestFunction21;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoFloat','F','F',@DoEchoFloat);
+  FP.Expression:='EchoFloat(1.234)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtFloat);
+  AssertResult(1.234);
+end;
+
+procedure TTestParserFunctions.TestFunction22;
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('EchoString','S','S',@DoEchoString);
+  FP.Expression:='EchoString(''Aloha'')';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtString);
+  AssertResult('Aloha');
+end;
+
+procedure TTestParserFunctions.TestFunction23;
+
+Var
+  I : TFPExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Date;
+  I:=FP.Identifiers.AddFunction('Date','D','',@DoGetDate);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtDateTime,I.ResultType);
+  FP.Expression:='Date';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtDateTime);
+  AssertDateTimeResult(D);
+end;
+
+procedure TTestParserFunctions.TestFunction24;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('AddInteger','I','II',@DoAddInteger);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtInteger,I.ResultType);
+  FP.Expression:='AddInteger(1,2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(3);
+end;
+
+procedure TTestParserFunctions.TestFunction25;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('Delete','S','SII',@DoDeleteString);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtString,I.ResultType);
+  FP.Expression:='Delete(''ABCDEFGHIJ'',3,2)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtString);
+  AssertResult('ABEFGHIJ');
+end;
+
+procedure TTestParserFunctions.TestFunction26;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('AddInteger','I','II',@DoAddInteger);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtInteger,I.ResultType);
+  FP.Expression:='AddInteger(1,2+3)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(6);
+end;
+
+procedure TTestParserFunctions.TestFunction27;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('AddInteger','I','II',@DoAddInteger);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtInteger,I.ResultType);
+  FP.Expression:='AddInteger(1+2,3*4)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(15);
+end;
+
+procedure TTestParserFunctions.TestFunction28;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  I:=FP.Identifiers.AddFunction('AddInteger','I','II',@DoAddInteger);
+  AssertEquals('List is dirty',True,FP.Dirty);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FP.Identifiers.Count);
+  AssertSame('Result equals variable added',I,FP.Identifiers[0]);
+  AssertEquals('Function has correct resulttype',rtInteger,I.ResultType);
+  FP.Expression:='AddInteger(3 and 2,3*4)';
+  AssertNotNull('Have result node',FP.ExprNode);
+  AssertNodeType('Constant expression',TFPFunctionEventHandler, FP.ExprNode);
+  AssertResultType(rtInteger);
+  AssertResult(14);
+end;
+
+procedure TTestParserFunctions.TestFunction29;
+
+Var
+  I : TFPExprIdentifierDef;
+
+begin
+  // Test type mismatch
+  I:=FP.Identifiers.AddFunction('AddInteger','I','II',@DoAddInteger);
+  TestParser('AddInteger(3 and 2,''s'')');
+end;
+
+{ TTestBuiltinsManager }
+
+procedure TTestBuiltinsManager.Setup;
+begin
+  inherited Setup;
+  FM:=TExprBuiltInManager.Create(Nil);
+end;
+
+procedure TTestBuiltinsManager.Teardown;
+begin
+  FreeAndNil(FM);
+  inherited Teardown;
+end;
+
+procedure TTestBuiltinsManager.TestCreate;
+begin
+  AssertEquals('Have no builtin expressions',0,FM.IdentifierCount);
+end;
+
+procedure TTestBuiltinsManager.TestVariable1;
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.AddVariable(bcuser,'a',rtBoolean,'True');
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Variable has correct resulttype',rtBoolean,I.ResultType);
+  AssertEquals('Variable has correct value','True',I.Value);
+end;
+
+procedure TTestBuiltinsManager.TestVariable2;
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.AddBooleanVariable(bcUser,'a',False);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Variable has correct resulttype',rtBoolean,I.ResultType);
+  AssertEquals('Variable has correct value','False',I.Value);
+end;
+
+procedure TTestBuiltinsManager.TestVariable3;
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.AddIntegerVariable(bcUser,'a',123);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Variable has correct resulttype',rtInteger,I.ResultType);
+  AssertEquals('Variable has correct value','123',I.Value);
+end;
+
+procedure TTestBuiltinsManager.TestVariable4;
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.AddFloatVariable(bcUser,'a',1.23);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Variable has correct resulttype',rtFloat,I.ResultType);
+  AssertEquals('Variable has correct value',FloatToStr(1.23),I.Value);
+end;
+
+procedure TTestBuiltinsManager.TestVariable5;
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.AddStringVariable(bcUser,'a','1.23');
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Variable has correct resulttype',rtString,I.ResultType);
+  AssertEquals('Variable has correct value','1.23',I.Value);
+end;
+
+procedure TTestBuiltinsManager.TestVariable6;
+Var
+  I : TFPBuiltinExprIdentifierDef;
+  D : TDateTime;
+
+begin
+  D:=Now;
+  I:=FM.AddDateTimeVariable(bcUser,'a',D);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Variable has correct resulttype',rtDateTime,I.ResultType);
+  AssertEquals('Variable has correct value',FormatDateTime('cccc',D),I.Value);
+end;
+
+procedure TTestBuiltinsManager.TestFunction1;
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.AddFunction(bcUser,'Date','D','',@GetDate);
+  AssertNotNull('Addvariable returns result',I);
+  AssertEquals('One variable added',1,FM.IdentifierCount);
+  AssertSame('Result equals variable added',I,FM.Identifiers[0]);
+  AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
+  AssertEquals('Function has correct resulttype',rtDateTime,I.ResultType);
+  AssertSame('Function has correct address',Pointer(@GetDate),Pointer(I.OnGetFunctionValueCallBack));
+end;
+
+procedure TTestBuiltinsManager.TestFunction2;
+
+Var
+  I,I2 : TFPBuiltinExprIdentifierDef;
+  ind : Integer;
+
+begin
+  FM.AddFunction(bcUser,'EchoDate','D','D',@EchoDate);
+  I:=FM.AddFunction(bcUser,'Echo','D','D',@EchoDate);
+  FM.AddFunction(bcUser,'DoEcho','D','D',@EchoDate);
+  ind:=FM.IndexOfIdentifier('Echo');
+  AssertEquals('Found identifier',1,ind);
+  I2:=FM.FindIdentifier('Echo');
+  AssertNotNull('FindIdentifier returns result',I2);
+  AssertSame('Findidentifier returns correct result',I,I2);
+  ind:=FM.IndexOfIdentifier('NoNoNo');
+  AssertEquals('Found no such identifier',-1,ind);
+  I2:=FM.FindIdentifier('NoNoNo');
+  AssertNull('FindIdentifier returns no result',I2);
+end;
+
+{ TTestBuiltins }
+
+procedure TTestBuiltins.Setup;
+begin
+  inherited Setup;
+  FM:=TExprBuiltInManager.Create(Nil);
+end;
+
+procedure TTestBuiltins.Teardown;
+begin
+  FreeAndNil(FM);
+  inherited Teardown;
+end;
+
+procedure TTestBuiltins.SetExpression(Const AExpression : String);
+
+Var
+  Msg : String;
+
+begin
+  Msg:='';
+  try
+    FP.Expression:=AExpression;
+  except
+    On E : Exception do
+      Msg:=E.message;
+  end;
+  If (Msg<>'') then
+    Fail('Parsing of expression "'+AExpression+'" failed :'+Msg);
+end;
+
+procedure TTestBuiltins.AssertVariable(const ADefinition: String;
+  AResultType: TResultType);
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.FindIdentifier(ADefinition);
+  AssertNotNull('Definition '+ADefinition+' is present.',I);
+  AssertEquals('Correct result type',AResultType,I.ResultType);
+end;
+
+procedure TTestBuiltins.AssertFunction(const ADefinition, AResultType,
+  ArgumentTypes: String; ACategory : TBuiltinCategory);
+
+Var
+  I : TFPBuiltinExprIdentifierDef;
+
+begin
+  I:=FM.FindIdentifier(ADefinition);
+  AssertEquals('Correct result type for test',1,Length(AResultType));
+  AssertNotNull('Definition '+ADefinition+' is present.',I);
+  AssertEquals(ADefinition+' has correct parameter types',ArgumentTypes,I.ParameterTypes);
+  AssertEquals(ADefinition+' has correct result type',CharToResultType(AResultType[1]),I.ResultType);
+  AssertEquals(ADefinition+' has correct category',Ord(ACategory),Ord(I.Category));
+end;
+
+procedure TTestBuiltins.AssertExpression(const AExpression: String;
+  AResult: Int64);
+
+begin
+  FP.BuiltIns:=AllBuiltIns;
+  SetExpression(AExpression);
+  AssertResult(AResult);
+end;
+
+procedure TTestBuiltins.AssertExpression(const AExpression: String;
+  const AResult: String);
+begin
+  FP.BuiltIns:=AllBuiltIns;
+  SetExpression(AExpression);
+  AssertResult(AResult);
+end;
+
+procedure TTestBuiltins.AssertExpression(const AExpression: String;
+  const AResult: TExprFloat);
+begin
+  FP.BuiltIns:=AllBuiltIns;
+  SetExpression(AExpression);
+  AssertResult(AResult);
+end;
+
+procedure TTestBuiltins.AssertExpression(const AExpression: String;
+  const AResult: Boolean);
+begin
+  FP.BuiltIns:=AllBuiltIns;
+  SetExpression(AExpression);
+  AssertResult(AResult);
+end;
+
+procedure TTestBuiltins.AssertDateTimeExpression(const AExpression: String;
+  const AResult: TDateTime);
+begin
+  FP.BuiltIns:=AllBuiltIns;
+  SetExpression(AExpression);
+  AssertDatetimeResult(AResult);
+end;
+
+procedure TTestBuiltins.TestRegister;
+
+begin
+  RegisterStdBuiltins(FM);
+  AssertEquals('Correct number of identifiers',64,FM.IdentifierCount);
+  Assertvariable('pi',rtFloat);
+  AssertFunction('cos','F','F',bcMath);
+  AssertFunction('sin','F','F',bcMath);
+  AssertFunction('arctan','F','F',bcMath);
+  AssertFunction('abs','F','F',bcMath);
+  AssertFunction('sqr','F','F',bcMath);
+  AssertFunction('sqrt','F','F',bcMath);
+  AssertFunction('exp','F','F',bcMath);
+  AssertFunction('ln','F','F',bcMath);
+  AssertFunction('log','F','F',bcMath);
+  AssertFunction('frac','F','F',bcMath);
+  AssertFunction('int','F','F',bcMath);
+  AssertFunction('round','I','F',bcMath);
+  AssertFunction('trunc','I','F',bcMath);
+  AssertFunction('length','I','S',bcStrings);
+  AssertFunction('copy','S','SII',bcStrings);
+  AssertFunction('delete','S','SII',bcStrings);
+  AssertFunction('pos','I','SS',bcStrings);
+  AssertFunction('lowercase','S','S',bcStrings);
+  AssertFunction('uppercase','S','S',bcStrings);
+  AssertFunction('stringreplace','S','SSSBB',bcStrings);
+  AssertFunction('comparetext','I','SS',bcStrings);
+  AssertFunction('date','D','',bcDateTime);
+  AssertFunction('time','D','',bcDateTime);
+  AssertFunction('now','D','',bcDateTime);
+  AssertFunction('dayofweek','I','D',bcDateTime);
+  AssertFunction('extractyear','I','D',bcDateTime);
+  AssertFunction('extractmonth','I','D',bcDateTime);
+  AssertFunction('extractday','I','D',bcDateTime);
+  AssertFunction('extracthour','I','D',bcDateTime);
+  AssertFunction('extractmin','I','D',bcDateTime);
+  AssertFunction('extractsec','I','D',bcDateTime);
+  AssertFunction('extractmsec','I','D',bcDateTime);
+  AssertFunction('encodedate','D','III',bcDateTime);
+  AssertFunction('encodetime','D','IIII',bcDateTime);
+  AssertFunction('encodedatetime','D','IIIIIII',bcDateTime);
+  AssertFunction('shortdayname','S','I',bcDateTime);
+  AssertFunction('shortmonthname','S','I',bcDateTime);
+  AssertFunction('longdayname','S','I',bcDateTime);
+  AssertFunction('longmonthname','S','I',bcDateTime);
+  AssertFunction('formatdatetime','S','SD',bcDateTime);
+  AssertFunction('shl','I','II',bcBoolean);
+  AssertFunction('shr','I','II',bcBoolean);
+  AssertFunction('IFS','S','BSS',bcBoolean);
+  AssertFunction('IFF','F','BFF',bcBoolean);
+  AssertFunction('IFD','D','BDD',bcBoolean);
+  AssertFunction('IFI','I','BII',bcBoolean);
+  AssertFunction('inttostr','S','I',bcConversion);
+  AssertFunction('strtoint','I','S',bcConversion);
+  AssertFunction('strtointdef','I','SI',bcConversion);
+  AssertFunction('floattostr','S','F',bcConversion);
+  AssertFunction('strtofloat','F','S',bcConversion);
+  AssertFunction('strtofloatdef','F','SF',bcConversion);
+  AssertFunction('booltostr','S','B',bcConversion);
+  AssertFunction('strtobool','B','S',bcConversion);
+  AssertFunction('strtobooldef','B','SB',bcConversion);
+  AssertFunction('datetostr','S','D',bcConversion);
+  AssertFunction('timetostr','S','D',bcConversion);
+  AssertFunction('strtodate','D','S',bcConversion);
+  AssertFunction('strtodatedef','D','SD',bcConversion);
+  AssertFunction('strtotime','D','S',bcConversion);
+  AssertFunction('strtotimedef','D','SD',bcConversion);
+  AssertFunction('strtodatetime','D','S',bcConversion);
+  AssertFunction('strtodatetimedef','D','SD',bcConversion);
+end;
+
+procedure TTestBuiltins.TestVariablepi;
+begin
+  AssertExpression('pi',Pi);
+end;
+
+procedure TTestBuiltins.TestFunctioncos;
+begin
+  AssertExpression('cos(0.5)',Cos(0.5));
+  AssertExpression('cos(0.75)',Cos(0.75));
+end;
+
+procedure TTestBuiltins.TestFunctionsin;
+begin
+  AssertExpression('sin(0.5)',sin(0.5));
+  AssertExpression('sin(0.75)',sin(0.75));
+end;
+
+procedure TTestBuiltins.TestFunctionarctan;
+begin
+  AssertExpression('arctan(0.5)',arctan(0.5));
+  AssertExpression('arctan(0.75)',arctan(0.75));
+end;
+
+procedure TTestBuiltins.TestFunctionabs;
+begin
+  AssertExpression('abs(0.5)',0.5);
+  AssertExpression('abs(-0.75)',0.75);
+end;
+
+procedure TTestBuiltins.TestFunctionsqr;
+begin
+  AssertExpression('sqr(0.5)',sqr(0.5));
+  AssertExpression('sqr(-0.75)',sqr(0.75));
+end;
+
+procedure TTestBuiltins.TestFunctionsqrt;
+begin
+  AssertExpression('sqrt(0.5)',sqrt(0.5));
+  AssertExpression('sqrt(0.75)',sqrt(0.75));
+end;
+
+procedure TTestBuiltins.TestFunctionexp;
+begin
+  AssertExpression('exp(1.0)',exp(1));
+  AssertExpression('exp(0.0)',1.0);
+end;
+
+procedure TTestBuiltins.TestFunctionln;
+begin
+  AssertExpression('ln(0.5)',ln(0.5));
+  AssertExpression('ln(1.5)',ln(1.5));
+end;
+
+procedure TTestBuiltins.TestFunctionlog;
+begin
+  AssertExpression('log(0.5)',ln(0.5)/ln(10.0));
+  AssertExpression('log(1.5)',ln(1.5)/ln(10.0));
+  AssertExpression('log(10.0)',1.0);
+end;
+
+procedure TTestBuiltins.TestFunctionfrac;
+begin
+  AssertExpression('frac(0.5)',frac(0.5));
+  AssertExpression('frac(1.5)',frac(1.5));
+end;
+
+procedure TTestBuiltins.TestFunctionint;
+begin
+  AssertExpression('int(0.5)',int(0.5));
+  AssertExpression('int(1.5)',int(1.5));
+end;
+
+procedure TTestBuiltins.TestFunctionround;
+begin
+  AssertExpression('round(0.5)',round(0.5));
+  AssertExpression('round(1.55)',round(1.55));
+end;
+
+procedure TTestBuiltins.TestFunctiontrunc;
+begin
+  AssertExpression('trunc(0.5)',trunc(0.5));
+  AssertExpression('trunc(1.55)',trunc(1.55));
+end;
+
+procedure TTestBuiltins.TestFunctionlength;
+begin
+  AssertExpression('length(''123'')',3);
+end;
+
+procedure TTestBuiltins.TestFunctioncopy;
+begin
+  AssertExpression('copy(''123456'',2,4)','2345');
+end;
+
+procedure TTestBuiltins.TestFunctiondelete;
+begin
+  AssertExpression('delete(''123456'',2,4)','16');
+end;
+
+procedure TTestBuiltins.TestFunctionpos;
+begin
+  AssertExpression('pos(''234'',''123456'')',2);
+end;
+
+procedure TTestBuiltins.TestFunctionlowercase;
+begin
+  AssertExpression('lowercase(''AbCdEf'')','abcdef');
+end;
+
+procedure TTestBuiltins.TestFunctionuppercase;
+begin
+  AssertExpression('uppercase(''AbCdEf'')','ABCDEF');
+end;
+
+procedure TTestBuiltins.TestFunctionstringreplace;
+begin
+  // last options are replaceall, ignorecase
+  AssertExpression('stringreplace(''AbCdEf'',''C'',''Z'',false,false)','AbZdEf');
+  AssertExpression('stringreplace(''AbCdEf'',''c'',''Z'',false,false)','AbCdEf');
+  AssertExpression('stringreplace(''AbCdEf'',''c'',''Z'',false,true)','AbZdEf');
+  AssertExpression('stringreplace(''AbCdEfC'',''C'',''Z'',false,false)','AbZdEfC');
+  AssertExpression('stringreplace(''AbCdEfC'',''C'',''Z'',True,false)','AbZdEfZ');
+end;
+
+procedure TTestBuiltins.TestFunctioncomparetext;
+begin
+  AssertExpression('comparetext(''AbCdEf'',''AbCdEf'')',0);
+  AssertExpression('comparetext(''AbCdEf'',''ABCDEF'')',0);
+  AssertExpression('comparetext(''AbCdEf'',''FEDCBA'')',comparetext('AbCdEf','FEDCBA'));
+end;
+
+procedure TTestBuiltins.TestFunctiondate;
+begin
+  AssertExpression('date',date);
+end;
+
+procedure TTestBuiltins.TestFunctiontime;
+begin
+  AssertExpression('time',time);
+end;
+
+procedure TTestBuiltins.TestFunctionnow;
+begin
+  AssertExpression('now',now);
+end;
+
+procedure TTestBuiltins.TestFunctiondayofweek;
+begin
+  FP.Identifiers.AddDateTimeVariable('D',Date);
+  AssertExpression('dayofweek(d)',DayOfWeek(date));
+end;
+
+procedure TTestBuiltins.TestFunctionextractyear;
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Date,Y,M,D);
+  FP.Identifiers.AddDateTimeVariable('D',Date);
+  AssertExpression('extractyear(d)',Y);
+end;
+
+procedure TTestBuiltins.TestFunctionextractmonth;
+
+Var
+  Y,M,D : Word;
+
+begin
+  FP.Identifiers.AddDateTimeVariable('D',Date);
+  DecodeDate(Date,Y,M,D);
+  AssertExpression('extractmonth(d)',M);
+end;
+
+procedure TTestBuiltins.TestFunctionextractday;
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Date,Y,M,D);
+  FP.Identifiers.AddDateTimeVariable('D',Date);
+  AssertExpression('extractday(d)',D);
+end;
+
+procedure TTestBuiltins.TestFunctionextracthour;
+
+Var
+  T : TDateTime;
+  H,m,s,ms : Word;
+
+begin
+  T:=Time;
+  DecodeTime(T,h,m,s,ms);
+  FP.Identifiers.AddDateTimeVariable('T',T);
+  AssertExpression('extracthour(t)',h);
+end;
+
+procedure TTestBuiltins.TestFunctionextractmin;
+Var
+  T : TDateTime;
+  H,m,s,ms : Word;
+
+begin
+  T:=Time;
+  DecodeTime(T,h,m,s,ms);
+  FP.Identifiers.AddDateTimeVariable('T',T);
+  AssertExpression('extractmin(t)',m);
+end;
+
+procedure TTestBuiltins.TestFunctionextractsec;
+Var
+  T : TDateTime;
+  H,m,s,ms : Word;
+
+begin
+  T:=Time;
+  DecodeTime(T,h,m,s,ms);
+  FP.Identifiers.AddDateTimeVariable('T',T);
+  AssertExpression('extractsec(t)',s);
+end;
+
+procedure TTestBuiltins.TestFunctionextractmsec;
+Var
+  T : TDateTime;
+  H,m,s,ms : Word;
+
+begin
+  T:=Time;
+  DecodeTime(T,h,m,s,ms);
+  FP.Identifiers.AddDateTimeVariable('T',T);
+  AssertExpression('extractmsec(t)',ms);
+end;
+
+procedure TTestBuiltins.TestFunctionencodedate;
+begin
+  AssertExpression('encodedate(2008,10,11)',EncodeDate(2008,10,11));
+end;
+
+procedure TTestBuiltins.TestFunctionencodetime;
+begin
+  AssertExpression('encodetime(14,10,11,0)',EncodeTime(14,10,11,0));
+end;
+
+procedure TTestBuiltins.TestFunctionencodedatetime;
+begin
+  AssertExpression('encodedatetime(2008,12,13,14,10,11,0)',EncodeDate(2008,12,13)+EncodeTime(14,10,11,0));
+end;
+
+procedure TTestBuiltins.TestFunctionshortdayname;
+begin
+  AssertExpression('shortdayname(1)',ShortDayNames[1]);
+  AssertExpression('shortdayname(7)',ShortDayNames[7]);
+end;
+
+procedure TTestBuiltins.TestFunctionshortmonthname;
+begin
+  AssertExpression('shortmonthname(1)',ShortMonthNames[1]);
+  AssertExpression('shortmonthname(12)',ShortMonthNames[12]);
+end;
+
+procedure TTestBuiltins.TestFunctionlongdayname;
+begin
+  AssertExpression('longdayname(1)',longDayNames[1]);
+  AssertExpression('longdayname(7)',longDayNames[7]);
+end;
+
+procedure TTestBuiltins.TestFunctionlongmonthname;
+begin
+  AssertExpression('longmonthname(1)',longMonthNames[1]);
+  AssertExpression('longmonthname(12)',longMonthNames[12]);
+end;
+
+procedure TTestBuiltins.TestFunctionformatdatetime;
+begin
+  AssertExpression('FormatDateTime(''cccc'',Date)',FormatDateTime('cccc',Date));
+end;
+
+procedure TTestBuiltins.TestFunctionshl;
+
+Var
+  I : Int64;
+
+begin
+  AssertExpression('shl(12,3)',12 shl 3);
+  I:=12 shl 30;
+  AssertExpression('shl(12,30)',I);
+end;
+
+procedure TTestBuiltins.TestFunctionshr;
+begin
+  AssertExpression('shr(12,2)',12 shr 2);
+end;
+
+procedure TTestBuiltins.TestFunctionIFS;
+begin
+  AssertExpression('ifs(true,''string1'',''string2'')','string1');
+  AssertExpression('ifs(false,''string1'',''string2'')','string2');
+end;
+
+procedure TTestBuiltins.TestFunctionIFF;
+begin
+  AssertExpression('iff(true,1.0,2.0)',1.0);
+  AssertExpression('iff(false,1.0,2.0)',2.0);
+end;
+
+procedure TTestBuiltins.TestFunctionIFD;
+begin
+  FP.Identifiers.AddDateTimeVariable('A',Date);
+  FP.Identifiers.AddDateTimeVariable('B',Date-1);
+  AssertExpression('ifd(true,A,B)',Date);
+  AssertExpression('ifd(false,A,B)',Date-1);
+end;
+
+procedure TTestBuiltins.TestFunctionIFI;
+begin
+  AssertExpression('ifi(true,1,2)',1);
+  AssertExpression('ifi(false,1,2)',2);
+end;
+
+procedure TTestBuiltins.TestFunctioninttostr;
+begin
+  AssertExpression('inttostr(2)','2');
+end;
+
+procedure TTestBuiltins.TestFunctionstrtoint;
+begin
+  AssertExpression('strtoint(''2'')',2);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtointdef;
+begin
+  AssertExpression('strtointdef(''abc'',2)',2);
+end;
+
+procedure TTestBuiltins.TestFunctionfloattostr;
+begin
+  AssertExpression('floattostr(1.23)',Floattostr(1.23));
+end;
+
+procedure TTestBuiltins.TestFunctionstrtofloat;
+
+Var
+  S : String;
+
+begin
+  S:='1.23';
+  S[2]:=DecimalSeparator;
+  AssertExpression('strtofloat('''+S+''')',1.23);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtofloatdef;
+
+begin
+  AssertExpression('strtofloatdef(''abc'',1.23)',1.23);
+end;
+
+procedure TTestBuiltins.TestFunctionbooltostr;
+begin
+  AssertExpression('strtofloatdef(''abc'',1.23)',1.23);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtobool;
+begin
+  AssertExpression('strtobool(''0'')',false);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtobooldef;
+begin
+  AssertExpression('strtobooldef(''XYZ'',True)',True);
+end;
+
+procedure TTestBuiltins.TestFunctiondatetostr;
+begin
+  FP.Identifiers.AddDateTimeVariable('A',Date);
+  AssertExpression('DateToStr(A)',DateToStr(Date));
+end;
+
+procedure TTestBuiltins.TestFunctiontimetostr;
+
+Var
+  T : TDateTime;
+
+begin
+  T:=Time;
+  FP.Identifiers.AddDateTimeVariable('A',T);
+  AssertExpression('TimeToStr(A)',TimeToStr(T));
+end;
+
+procedure TTestBuiltins.TestFunctionstrtodate;
+
+begin
+  FP.Identifiers.AddStringVariable('S',DateToStr(Date));
+  AssertExpression('StrToDate(S)',Date);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtodatedef;
+begin
+  FP.Identifiers.AddDateTimeVariable('A',Date);
+  AssertExpression('StrToDateDef(''S'',A)',Date);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtotime;
+
+Var
+  T : TDateTime;
+
+begin
+  T:=Time;
+  FP.Identifiers.AddStringVariable('S',TimeToStr(T));
+  AssertExpression('StrToTime(S)',T);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtotimedef;
+Var
+  T : TDateTime;
+
+begin
+  T:=Time;
+  FP.Identifiers.AddDateTimeVariable('S',T);
+  AssertExpression('StrToTimeDef(''q'',S)',T);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtodatetime;
+
+Var
+  T : TDateTime;
+  S : String;
+
+begin
+  T:=Now;
+  S:=DateTimetostr(T);
+  AssertExpression('StrToDateTime('''+S+''')',T);
+end;
+
+procedure TTestBuiltins.TestFunctionstrtodatetimedef;
+
+Var
+  T : TDateTime;
+  S : String;
+
+begin
+  T:=Now;
+  S:=DateTimetostr(T);
+  FP.Identifiers.AddDateTimeVariable('S',T);
+  AssertExpression('StrToDateTimeDef('''+S+''',S)',T);
+end;
+
+{ TTestNotNode }
+
+procedure TTestNotNode.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestNotNode.TestCreateInteger;
+begin
+  FN:=TFPNotNode.Create(CreateIntNode(3));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',Not(Int64(3)),FN.NodeValue.ResInteger);
+end;
+
+procedure TTestNotNode.TestCreateBoolean;
+begin
+  FN:=TFPNotNode.Create(CreateBoolNode(True));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',False,FN.NodeValue.ResBoolean);
+end;
+
+procedure TTestNotNode.TestCreateString;
+begin
+  FN:=TFPNotNode.Create(CreateStringNode('True'));
+  AssertNodeNotOK('String node type',FN);
+end;
+
+procedure TTestNotNode.TestCreateFloat;
+begin
+  FN:=TFPNotNode.Create(CreateFloatNode(1.23));
+  AssertNodeNotOK('String node type',FN);
+end;
+
+procedure TTestNotNode.TestCreateDateTime;
+begin
+  FN:=TFPNotNode.Create(CreateDateTimeNode(Now));
+  AssertNodeNotOK('String node type',FN);
+end;
+
+procedure TTestNotNode.TestDestroy;
+begin
+  FN:=TFPNotNode.Create(TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for operand',1,self.FDestroyCalled)
+end;
+
+{ TTestIfOperation }
+
+procedure TTestIfOperation.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestIfOperation.TestCreateInteger;
+begin
+  FN:=TIfOperation.Create(CreateIntNode(1),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeNotOK('First argument wrong',FN);
+end;
+
+procedure TTestIfOperation.TestCreateBoolean;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(True),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',2,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestIfOperation.TestCreateBoolean2;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(False),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',3,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestIfOperation.TestCreateBooleanInteger;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(False),CreateIntNode(2),CreateBoolNode(False));
+  AssertNodeNotOK('Arguments differ in type',FN);
+end;
+
+procedure TTestIfOperation.TestCreateBooleanInteger2;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(True),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',2,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestIfOperation.TestCreateBooleanString;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(True),CreateStringNode('2'),CreateStringNode('3'));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','2',FN.NodeValue.ResString);
+end;
+
+procedure TTestIfOperation.TestCreateBooleanString2;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(False),CreateStringNode('2'),CreateStringNode('3'));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','3',FN.NodeValue.ResString);
+end;
+
+procedure TTestIfOperation.TestCreateBooleanDateTime;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(True),CreateDateTimeNode(Date),CreateDateTimeNode(Date-1));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtDateTime,FN.NodeType);
+  AssertEquals('Correct result',Date,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestIfOperation.TestCreateBooleanDateTime2;
+begin
+  FN:=TIfOperation.Create(CreateBoolNode(False),CreateDateTimeNode(Date),CreateDateTimeNode(Date-1));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtDateTime,FN.NodeType);
+  AssertEquals('Correct result',Date-1,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestIfOperation.TestCreateString;
+begin
+  FN:=TIfOperation.Create(CreateStringNode('1'),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeNotOK('First argument wrong',FN);
+end;
+
+procedure TTestIfOperation.TestCreateFloat;
+begin
+  FN:=TIfOperation.Create(CreateFloatNode(2.0),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeNotOK('First argument wrong',FN);
+end;
+
+procedure TTestIfOperation.TestCreateDateTime;
+begin
+  FN:=TIfOperation.Create(CreateDateTimeNode(Date),CreateIntNode(2),CreateIntNode(3));
+  AssertNodeNotOK('First argument wrong',FN);
+end;
+
+procedure TTestIfOperation.TestDestroy;
+begin
+  FN:=TIfOperation.Create(TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self),TMyDestroyNode.CreateTest(Self));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for operand',3,self.FDestroyCalled)
+end;
+
+{ TTestCaseOperation }
+
+function TTestCaseOperation.CreateArgs(
+  Args: array of const): TExprArgumentArray;
+
+Var
+  I : Integer;
+
+begin
+  SetLength(Result,High(Args)-Low(Args)+1);
+  For I:=Low(Args) to High(Args) do
+    Result[I]:=Args[i].VObject as TFPExprNode;
+end;
+
+procedure TTestCaseOperation.TearDown;
+begin
+  FreeAndNil(FN);
+  inherited TearDown;
+end;
+
+procedure TTestCaseOperation.TestCreateOne;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(False)]));
+  AssertNodeNotOK('Too little arguments',FN);
+end;
+
+procedure TTestCaseOperation.TestCreateTwo;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(False),CreateBoolNode(False)]));
+  AssertNodeNotOK('Too little arguments',FN);
+end;
+
+procedure TTestCaseOperation.TestCreateThree;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(False),CreateBoolNode(False),CreateBoolNode(False)]));
+  AssertNodeNotOK('Too little arguments',FN);
+end;
+
+procedure TTestCaseOperation.TestCreateOdd;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(False),CreateBoolNode(False),
+                                        CreateBoolNode(False),CreateBoolNode(False),
+                                        CreateBoolNode(False)]));
+  AssertNodeNotOK('Odd number of arguments',FN);
+end;
+
+procedure TTestCaseOperation.TestCreateNoExpression;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(False),
+                                        CreateBoolNode(False),
+                                        TFPBinaryOrOperation.Create(CreateBoolNode(False),CreateBoolNode(False)),
+                                        CreateBoolNode(False)]));
+  AssertNodeNotOK('Label is not a constant expression',FN);
+end;
+
+procedure TTestCaseOperation.TestCreateWrongLabel;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateBoolNode(False),
+                                        CreateIntNode(1),CreateBoolNode(False),
+                                        CreateBoolNode(True),CreateBoolNode(False)]));
+  AssertNodeNotOK('Wrong label',FN);
+end;
+
+procedure TTestCaseOperation.TestCreateWrongValue;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateBoolNode(False),
+                                        CreateIntNode(1),CreateBoolNode(False),
+                                        CreateIntNode(2),CreateIntNode(1)]));
+  AssertNodeNotOK('Wrong value',FN);
+end;
+
+procedure TTestCaseOperation.TestIntegerTag;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateStringNode('many'),
+                                        CreateIntNode(1),CreateStringNode('one'),
+                                        CreateIntNode(2),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','one',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestIntegerTagDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(3),CreateStringNode('many'),
+                                        CreateIntNode(1),CreateStringNode('one'),
+                                        CreateIntNode(2),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','many',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestStringTag;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateStringNode('one'),CreateIntNode(3),
+                                        CreateStringNode('one'),CreateIntNode(1),
+                                        CreateStringNode('two'),CreateIntNode(2)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',1,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestCaseOperation.TestStringTagDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateStringNode('many'),CreateIntNode(3),
+                                        CreateStringNode('one'),CreateIntNode(1),
+                                        CreateStringNode('two'),CreateIntNode(2)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',3,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestCaseOperation.TestFloatTag;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateFloatNode(1.0),CreateStringNode('many'),
+                                        CreateFloatNode(1.0),CreateStringNode('one'),
+                                        CreateFloatNode(2.0),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','one',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestFloatTagDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateFloatNode(3.0),CreateStringNode('many'),
+                                        CreateFloatNode(1.0),CreateStringNode('one'),
+                                        CreateFloatNode(2.0),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','many',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestBooleanTag;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(True),CreateStringNode('unknown'),
+                                        CreateBoolNode(True),CreateStringNode('one'),
+                                        CreateBoolNode(False),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','one',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestBooleanTagDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateBoolNode(True),CreateStringNode('unknown'),
+                                        CreateBoolNode(False),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','unknown',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestDateTimeTag;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateDateTimeNode(Date),CreateStringNode('later'),
+                                        CreateDateTimeNode(Date),CreateStringNode('today'),
+                                        CreateDateTimeNode(Date+1),CreateStringNode('tomorrow')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','today',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestDateTimeTagDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateDateTimeNode(Date+2),CreateStringNode('later'),
+                                        CreateDateTimeNode(Date),CreateStringNode('today'),
+                                        CreateDateTimeNode(Date+1),CreateStringNode('tomorrow')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','later',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestIntegerValue;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateIntNode(0),
+                                        CreateIntNode(1),CreateIntNode(-1),
+                                        CreateIntNode(2),CreateIntNode(-2)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',-1,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestCaseOperation.TestIntegerValueDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(3),CreateIntNode(0),
+                                        CreateIntNode(1),CreateIntNode(-1),
+                                        CreateIntNode(2),CreateIntNode(-2)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtInteger,FN.NodeType);
+  AssertEquals('Correct result',0,FN.NodeValue.ResInteger);
+end;
+
+procedure TTestCaseOperation.TestStringValue;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateStringNode('many'),
+                                        CreateIntNode(1),CreateStringNode('one'),
+                                        CreateIntNode(2),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','one',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestStringValueDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(3),CreateStringNode('many'),
+                                        CreateIntNode(1),CreateStringNode('one'),
+                                        CreateIntNode(2),CreateStringNode('two')]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtString,FN.NodeType);
+  AssertEquals('Correct result','many',FN.NodeValue.ResString);
+end;
+
+procedure TTestCaseOperation.TestFloatValue;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateFloatNode(0.0),
+                                        CreateIntNode(1),CreateFloatNode(2.0),
+                                        CreateIntNode(2),CreateFloatNode(1.0)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtFloat,FN.NodeType);
+  AssertEquals('Correct result',2.0,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestCaseOperation.TestFloatValueDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(3),CreateFloatNode(0.0),
+                                        CreateIntNode(1),CreateFloatNode(2.0),
+                                        CreateIntNode(2),CreateFloatNode(1.0)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtFloat,FN.NodeType);
+  AssertEquals('Correct result',0.0,FN.NodeValue.ResFloat);
+end;
+
+procedure TTestCaseOperation.TestBooleanValue;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateBoolNode(False),
+                                        CreateIntNode(1),CreateBoolNode(True),
+                                        CreateIntNode(2),CreateBoolNode(False)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',True,FN.NodeValue.ResBoolean);
+end;
+
+procedure TTestCaseOperation.TestBooleanValueDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(3),CreateBoolNode(False),
+                                        CreateIntNode(1),CreateBoolNode(True),
+                                        CreateIntNode(2),CreateBoolNode(False)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtBoolean,FN.NodeType);
+  AssertEquals('Correct result',False,FN.NodeValue.ResBoolean);
+end;
+
+procedure TTestCaseOperation.TestDateTimeValue;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(1),CreateDateTimeNode(Date+1),
+                                        CreateIntNode(1),CreateDateTimeNode(Date),
+                                        CreateIntNode(2),CreateDateTimeNode(Date-1)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtDateTime,FN.NodeType);
+  AssertEquals('Correct result',Date,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestCaseOperation.TestDateTimeValueDefault;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([CreateIntNode(3),CreateDateTimeNode(Date+1),
+                                        CreateIntNode(1),CreateDateTimeNode(Date),
+                                        CreateIntNode(2),CreateDateTimeNode(Date-1)]));
+  AssertNodeOK(FN);
+  AssertEquals('Correct node type',rtDateTime,FN.NodeType);
+  AssertEquals('Correct result',Date+1,FN.NodeValue.ResDateTime);
+end;
+
+procedure TTestCaseOperation.TestDestroy;
+begin
+  FN:=TCaseOperation.Create(CreateArgs([TMyDestroyNode.CreateTest(Self),
+                                        TMyDestroyNode.CreateTest(Self),
+                                        TMyDestroyNode.CreateTest(Self),
+                                        TMyDestroyNode.CreateTest(Self)]));
+  FreeAndNil(FN);
+  AssertEquals('Destroy called for operand',4,self.FDestroyCalled)
+end;
+
+initialization
+
+  RegisterTests([TTestExpressionScanner, TTestDestroyNode,
+                 TTestConstExprNode,TTestNegateExprNode,
+                 TTestBinaryAndNode,TTestBinaryOrNode,TTestBinaryXOrNode,
+                 TTestNotNode,TTestEqualNode,TTestUnEqualNode,
+                 TTestIfOperation,TTestCaseOperation,
+                 TTestLessThanNode,TTestLessThanEqualNode,
+                 TTestLargerThanNode,TTestLargerThanEqualNode,
+                 TTestAddNode,TTestSubtractNode,
+                 TTestMultiplyNode,TTestDivideNode,
+                 TTestIntToFloatNode,TTestIntToDateTimeNode,
+                 TTestFloatToDateTimeNode,
+                 TTestParserExpressions, TTestParserBooleanOperations,
+                 TTestParserOperands, TTestParserTypeMatch,
+                 TTestParserVariables,TTestParserFunctions,
+                 TTestBuiltinsManager,TTestBuiltins]);
+end.
+

+ 1 - 0
packages/fcl-base/fpmake.pp

@@ -92,6 +92,7 @@ begin
         begin
           AddUnit('wformat');
         end;
+    T:=P.Targets.AddUnit('fpexprpars.pp');
 
     // Windows units
     T:=P.Targets.AddUnit('ServiceManager.pas',[Win32,Win64]);

+ 2 - 0
packages/fcl-base/src/daemonapp.pp

@@ -224,6 +224,7 @@ Type
     FOptions: TDaemonOptions;
     FServiceName: String;
     FWinBindings: TWinBindings;
+    FRunArgs : String;
     procedure SetName(const AValue: String);
     procedure SetWinBindings(const AValue: TWinBindings);
   Protected
@@ -237,6 +238,7 @@ Type
     Property DaemonClassName : String Read FDaemonClassName Write FDaemonClassName;
     Property Name : String Read FName Write SetName;
     Property DisplayName : String Read FDisplayName Write FDisplayName;
+    Property RunArguments : String Read FRunArgs Write FRunArgs;
     Property Options : TDaemonOptions Read FOptions Write FOptions;
     Property Enabled : Boolean Read FEnabled Write FEnabled default true;
     Property WinBindings : TWinBindings Read FWinBindings Write SetWinBindings;

+ 3406 - 0
packages/fcl-base/src/fpexprpars.pp

@@ -0,0 +1,3406 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2008 Michael Van Canneyt.
+
+    Expression parser, supports variables, functions and
+    float/integer/string/boolean/datetime operations.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{$mode objfpc}
+{$h+}
+unit fpexprpars;
+
+interface
+
+uses
+  Classes, SysUtils, contnrs;
+
+Type
+  // tokens
+  TTokenType = (ttPlus, ttMinus, ttLessThan, ttLargerThan, ttEqual, ttDiv,
+                ttMul, ttLeft, ttRight, ttLessThanEqual, ttLargerThanEqual,
+                ttunequal, ttNumber, ttString, ttIdentifier,
+                ttComma, ttand, ttOr,ttXor,ttTrue,ttFalse,ttnot,ttif,
+                ttCase,ttEOF);
+
+  TExprFloat = Double;
+
+Const
+  ttDelimiters = [ttPlus, ttMinus, ttLessThan, ttLargerThan, ttEqual, ttDiv,
+                  ttMul, ttLeft, ttRight, ttLessThanEqual, ttLargerThanEqual,
+                  ttunequal];
+  ttComparisons = [ttLargerThan,ttLessthan,
+                   ttLargerThanEqual,ttLessthanEqual,
+                   ttEqual,ttUnequal];
+
+Type
+
+  TFPExpressionParser = Class;
+  TExprBuiltInManager = Class;
+
+  { TFPExpressionScanner }
+
+  TFPExpressionScanner = Class(TObject)
+    FSource : String;
+    LSource,
+    FPos : Integer;
+    FChar : PChar;
+    FToken : String;
+    FTokenType : TTokenType;
+  private
+    function GetCurrentChar: Char;
+    procedure ScanError(Msg: String);
+  protected
+    procedure SetSource(const AValue: String); virtual;
+    function DoIdentifier: TTokenType;
+    function DoNumber: TTokenType;
+    function DoDelimiter: TTokenType;
+    function DoString: TTokenType;
+    Function NextPos : Char; // inline;
+    procedure SkipWhiteSpace; // inline;
+    function IsWordDelim(C : Char) : Boolean; // inline;
+    function IsDelim(C : Char) : Boolean; // inline;
+    function IsDigit(C : Char) : Boolean; // inline;
+    function IsAlpha(C : Char) : Boolean; // inline;
+  public
+    Constructor Create;
+    Function GetToken : TTokenType;
+    Property Token : String Read FToken;
+    Property TokenType : TTokenType Read FTokenType;
+    Property Source : String Read FSource Write SetSource;
+    Property Pos : Integer Read FPos;
+    Property CurrentChar : Char Read GetCurrentChar;
+  end;
+
+  EExprScanner = Class(Exception);
+
+  TResultType = (rtBoolean,rtInteger,rtFloat,rtDateTime,rtString);
+  TResultTypes = set of TResultType;
+
+  TFPExpressionResult = record
+    ResString   : String;
+    Case ResultType : TResultType of
+      rtBoolean  : (ResBoolean  : Boolean);
+      rtInteger  : (ResInteger  : Int64);
+      rtFloat    : (ResFloat    : TExprFloat);
+      rtDateTime : (ResDateTime : TDatetime);
+      rtString   : ();
+  end;
+  PFPExpressionResult = ^TFPExpressionResult;
+  TExprParameterArray = Array of TFPExpressionResult;
+
+  { TFPExprNode }
+
+  TFPExprNode = Class(TObject)
+  Protected
+    Procedure CheckNodeType(Anode : TFPExprNode; Allowed : TResultTypes);
+    // A procedure with var saves an implicit try/finally in each node
+    // A marked difference in execution speed.
+    Procedure GetNodeValue(var Result : TFPExpressionResult); virtual; abstract;
+  Public
+    Procedure Check; virtual; abstract;
+    Function NodeType : TResultType; virtual; abstract;
+    Function NodeValue : TFPExpressionResult;
+    Function AsString : string; virtual; abstract;
+  end;
+  TExprArgumentArray = Array of TFPExprNode;
+
+  { TFPBinaryOperation }
+
+  TFPBinaryOperation = Class(TFPExprNode)
+  private
+    FLeft: TFPExprNode;
+    FRight: TFPExprNode;
+  Protected
+    Procedure CheckSameNodeTypes;
+  Public
+    Constructor Create(ALeft,ARight : TFPExprNode);
+    Destructor Destroy; override;
+    Procedure Check; override;
+    Property left : TFPExprNode Read FLeft;
+    Property Right : TFPExprNode Read FRight;
+  end;
+  TFPBinaryOperationClass = Class of TFPBinaryOperation;
+
+
+  { TFPBooleanOperation }
+
+  TFPBooleanOperation = Class(TFPBinaryOperation)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  end;
+  { TFPBinaryAndOperation }
+
+  TFPBinaryAndOperation = Class(TFPBooleanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPBinaryOrOperation }
+
+  TFPBinaryOrOperation = Class(TFPBooleanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPBinaryXOrOperation }
+
+  TFPBinaryXOrOperation = Class(TFPBooleanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPBooleanResultOperation }
+
+  TFPBooleanResultOperation = Class(TFPBinaryOperation)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  end;
+  TFPBooleanResultOperationClass = Class of TFPBooleanResultOperation;
+
+
+  { TFPEqualOperation }
+
+  TFPEqualOperation = Class(TFPBooleanResultOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPUnequalOperation }
+
+  TFPUnequalOperation = Class(TFPEqualOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPOrderingOperation }
+
+  TFPOrderingOperation = Class(TFPBooleanResultOperation)
+    Procedure Check; override;
+  end;
+
+  { TFPLessThanOperation }
+
+  TFPLessThanOperation = Class(TFPOrderingOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPGreaterThanOperation }
+
+  TFPGreaterThanOperation = Class(TFPOrderingOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPLessThanEqualOperation }
+
+  TFPLessThanEqualOperation = Class(TFPGreaterThanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+
+  { TFPGreaterThanEqualOperation }
+
+  TFPGreaterThanEqualOperation = Class(TFPLessThanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TIfOperation }
+
+  TIfOperation = Class(TFPBinaryOperation)
+  private
+    FCondition: TFPExprNode;
+  protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  Public
+    Constructor Create(ACondition,ALeft,ARight : TFPExprNode);
+    Destructor destroy; override;
+    Function AsString : string ; override;
+    Property Condition : TFPExprNode Read FCondition;
+  end;
+
+  { TCaseOperation }
+
+  TCaseOperation = Class(TFPExprNode)
+  private
+    FArgs : TExprArgumentArray;
+    FCondition: TFPExprNode;
+  protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  Public
+    Constructor Create(Args : TExprArgumentArray);
+    Destructor destroy; override;
+    Function AsString : string ; override;
+    Property Condition : TFPExprNode Read FCondition;
+  end;
+
+  { TMathOperation }
+
+  TMathOperation = Class(TFPBinaryOperation)
+  protected
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  end;
+
+  { TFPAddOperation }
+
+  TFPAddOperation = Class(TMathOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPSubtractOperation }
+
+  TFPSubtractOperation = Class(TMathOperation)
+  Protected
+    Procedure check; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPMultiplyOperation }
+
+  TFPMultiplyOperation = Class(TMathOperation)
+  Protected
+    Procedure check; override;
+  Public
+    Function AsString : string ; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  end;
+
+  { TFPDivideOperation }
+
+  TFPDivideOperation = Class(TMathOperation)
+  Protected
+    Procedure check; override;
+  Public
+    Function AsString : string ; override;
+    Function NodeType : TResultType; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  end;
+
+  { TFPUnaryOperator }
+
+  TFPUnaryOperator = Class(TFPExprNode)
+  private
+    FOperand: TFPExprNode;
+  Public
+    Constructor Create(AOperand : TFPExprNode);
+    Destructor Destroy; override;
+    Procedure Check; override;
+    Property Operand : TFPExprNode Read FOperand;
+  end;
+
+  { TFPConvertNode }
+
+  TFPConvertNode = Class(TFPUnaryOperator)
+    Function AsString : String; override;
+  end;
+
+  { TFPNotNode }
+
+  TFPNotNode = Class(TFPUnaryOperator)
+  Protected
+    Procedure Check; override;
+  Public
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Function AsString : String; override;
+  end;
+
+  TIntConvertNode = Class(TFPConvertNode)
+  Protected
+    Procedure Check; override;
+  end;
+
+  { TIntToFloatNode }
+  TIntToFloatNode = Class(TIntConvertNode)
+  Public
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TIntToDateTimeNode }
+
+  TIntToDateTimeNode = Class(TIntConvertNode)
+  Public
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TFloatToDateTimeNode }
+
+  TFloatToDateTimeNode = Class(TFPConvertNode)
+  Protected
+    Procedure Check; override;
+  Public
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TFPNegateOperation }
+
+  TFPNegateOperation = Class(TFPUnaryOperator)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Function AsString : String; override;
+  end;
+
+  { TFPConstExpression }
+
+  TFPConstExpression = Class(TFPExprnode)
+  private
+    FValue : TFPExpressionResult;
+  public
+    Constructor CreateString(AValue : String);
+    Constructor CreateInteger(AValue : Int64);
+    Constructor CreateDateTime(AValue : TDateTime);
+    Constructor CreateFloat(AValue : TExprFloat);
+    Constructor CreateBoolean(AValue : Boolean);
+    Procedure Check; override;
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Function AsString : string ; override;
+   // For inspection
+    Property ConstValue : TFPExpressionResult read FValue;
+  end;
+
+
+  TIdentifierType = (itVariable,itFunctionCallBack,itFunctionHandler);
+  TFPExprFunctionCallBack = Procedure (Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+  TFPExprFunctionEvent = Procedure (Var Result : TFPExpressionResult; Const Args : TExprParameterArray) of object;
+
+  { TFPExprIdentifierDef }
+
+  TFPExprIdentifierDef = Class(TCollectionItem)
+  private
+    FStringValue : String;
+    FValue : TFPExpressionResult;
+    FArgumentTypes: String;
+    FIDType: TIdentifierType;
+    FName: ShortString;
+    FOnGetValue: TFPExprFunctionEvent;
+    FOnGetValueCB: TFPExprFunctionCallBack;
+    function GetAsBoolean: Boolean;
+    function GetAsDateTime: TDateTime;
+    function GetAsFloat: TExprFloat;
+    function GetAsInteger: Int64;
+    function GetAsString: String;
+    function GetResultType: TResultType;
+    function GetValue: String;
+    procedure SetArgumentTypes(const AValue: String);
+    procedure SetAsBoolean(const AValue: Boolean);
+    procedure SetAsDateTime(const AValue: TDateTime);
+    procedure SetAsFloat(const AValue: TExprFloat);
+    procedure SetAsInteger(const AValue: Int64);
+    procedure SetAsString(const AValue: String);
+    procedure SetName(const AValue: ShortString);
+    procedure SetResultType(const AValue: TResultType);
+    procedure SetValue(const AValue: String);
+  Protected
+    Procedure CheckResultType(Const AType : TResultType);
+    Procedure CheckVariable;
+  Public
+    Function ArgumentCount : Integer;
+    Procedure Assign(Source : TPersistent); override;
+    Property AsFloat : TExprFloat Read GetAsFloat Write SetAsFloat;
+    Property AsInteger : Int64 Read GetAsInteger Write SetAsInteger;
+    Property AsString : String Read GetAsString Write SetAsString;
+    Property AsBoolean : Boolean Read GetAsBoolean Write SetAsBoolean;
+    Property AsDateTime : TDateTime Read GetAsDateTime Write SetAsDateTime;
+    Property OnGetFunctionValueCallBack : TFPExprFunctionCallBack Read FOnGetValueCB Write FOnGetValueCB;
+  Published
+    Property IdentifierType : TIdentifierType Read FIDType Write FIDType;
+    Property Name : ShortString Read FName Write SetName;
+    Property Value : String Read GetValue Write SetValue;
+    Property ParameterTypes : String Read FArgumentTypes Write SetArgumentTypes;
+    Property ResultType : TResultType Read GetResultType Write SetResultType;
+    Property OnGetFunctionValue : TFPExprFunctionEvent Read FOnGetValue Write FOnGetValue;
+  end;
+
+
+  TBuiltInCategory = (bcStrings,bcDateTime,bcMath,bcBoolean,bcConversion,bcData,bcVaria,bcUser);
+  TBuiltInCategories = Set of TBuiltInCategory;
+
+  { TFPBuiltInExprIdentifierDef }
+
+  TFPBuiltInExprIdentifierDef = Class(TFPExprIdentifierDef)
+  private
+    FCategory: TBuiltInCategory;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+  Published
+    Property Category : TBuiltInCategory Read FCategory Write FCategory;
+  end;
+
+  { TFPExprIdentifierDefs }
+
+  TFPExprIdentifierDefs = Class(TCollection)
+  private
+    FParser: TFPExpressionParser;
+    function GetI(AIndex : Integer): TFPExprIdentifierDef;
+    procedure SetI(AIndex : Integer; const AValue: TFPExprIdentifierDef);
+  Protected
+    procedure Update(Item: TCollectionItem); override;
+    Property Parser: TFPExpressionParser Read FParser;
+  Public
+    Function IndexOfIdentifier(Const AName : ShortString) : Integer;
+    Function FindIdentifier(Const AName : ShortString) : TFPExprIdentifierDef;
+    Function IdentifierByName(Const AName : ShortString) : TFPExprIdentifierDef;
+    Function AddVariable(Const AName : ShortString; AResultType : TResultType; AValue : String) : TFPExprIdentifierDef;
+    Function AddBooleanVariable(Const AName : ShortString; AValue : Boolean) : TFPExprIdentifierDef;
+    Function AddIntegerVariable(Const AName : ShortString; AValue : Integer) : TFPExprIdentifierDef;
+    Function AddFloatVariable(Const AName : ShortString; AValue : TExprFloat) : TFPExprIdentifierDef;
+    Function AddStringVariable(Const AName : ShortString; AValue : String) : TFPExprIdentifierDef;
+    Function AddDateTimeVariable(Const AName : ShortString; AValue : TDateTime) : TFPExprIdentifierDef;
+    Function AddFunction(Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionCallBack) : TFPExprIdentifierDef;
+    Function AddFunction(Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionEvent) : TFPExprIdentifierDef;
+    property Identifiers[AIndex : Integer] : TFPExprIdentifierDef Read GetI Write SetI; Default;
+  end;
+
+  { TFPExprIdentifierNode }
+
+  TFPExprIdentifierNode = Class(TFPExprNode)
+  Private
+    FID : TFPExprIdentifierDef;
+    PResult : PFPExpressionResult;
+    FResultType : TResultType;
+  public
+    Constructor CreateIdentifier(AID : TFPExprIdentifierDef);
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Property Identifier : TFPExprIdentifierDef Read FID;
+  end;
+
+  { TFPExprVariable }
+
+  TFPExprVariable = Class(TFPExprIdentifierNode)
+    Procedure Check; override;
+    function AsString: string; override;
+  end;
+
+  { TFPExprFunction }
+
+  TFPExprFunction = Class(TFPExprIdentifierNode)
+  private
+    FArgumentNodes : TExprArgumentArray;
+    FargumentParams : TExprParameterArray;
+  Protected
+    Procedure CalcParams;
+    Procedure Check; override;
+  Public
+    Constructor CreateFunction(AID : TFPExprIdentifierDef; Const Args : TExprArgumentArray); virtual;
+    Destructor Destroy; override;
+    Property ArgumentNodes : TExprArgumentArray Read FArgumentNodes;
+    Property ArgumentParams : TExprParameterArray Read FArgumentParams;
+    Function AsString : String; override;
+  end;
+
+  { TFPFunctionCallBack }
+
+  TFPFunctionCallBack = Class(TFPExprFunction)
+  Private
+    FCallBack : TFPExprFunctionCallBack;
+  Public
+    Constructor CreateFunction(AID : TFPExprIdentifierDef; Const Args : TExprArgumentArray); override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Property CallBack : TFPExprFunctionCallBack Read FCallBack;
+  end;
+
+  { TFPFunctionEventHandler }
+
+  TFPFunctionEventHandler = Class(TFPExprFunction)
+  Private
+    FCallBack : TFPExprFunctionEvent;
+  Public
+    Constructor CreateFunction(AID : TFPExprIdentifierDef; Const Args : TExprArgumentArray); override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+    Property CallBack : TFPExprFunctionEvent Read FCallBack;
+  end;
+
+  { TFPExpressionParser }
+
+  TFPExpressionParser = class(TComponent)
+  private
+    FBuiltIns: TBuiltInCategories;
+    FExpression: String;
+    FScanner : TFPExpressionScanner;
+    FExprNode : TFPExprNode;
+    FIdentifiers : TFPExprIdentifierDefs;
+    FHashList : TFPHashObjectlist;
+    FDirty : Boolean;
+    procedure CheckEOF;
+    function ConvertNode(Todo: TFPExprNode; ToType: TResultType): TFPExprNode;
+    function GetAsBoolean: Boolean;
+    function GetAsDateTime: TDateTime;
+    function GetAsFloat: TExprFloat;
+    function GetAsInteger: Int64;
+    function GetAsString: String;
+    function MatchNodes(Todo, Match: TFPExprNode): TFPExprNode;
+    procedure CheckNodes(var Left, Right: TFPExprNode);
+    procedure SetBuiltIns(const AValue: TBuiltInCategories);
+    procedure SetIdentifiers(const AValue: TFPExprIdentifierDefs);
+  Protected
+    procedure ParserError(Msg: String);
+    procedure SetExpression(const AValue: String); virtual;
+    Procedure CheckResultType(Const Res :TFPExpressionResult; AType : TResultType); inline;
+    class Function BuiltinsManager : TExprBuiltInManager;
+    Function Level1 : TFPExprNode;
+    Function Level2 : TFPExprNode;
+    Function Level3 : TFPExprNode;
+    Function Level4 : TFPExprNode;
+    Function Level5 : TFPExprNode;
+    Function Level6 : TFPExprNode;
+    Function Primitive : TFPExprNode;
+    function GetToken: TTokenType;
+    Function TokenType : TTokenType;
+    Function CurrentToken : String;
+    Procedure CreateHashList;
+    Property Scanner : TFPExpressionScanner Read FScanner;
+    Property ExprNode : TFPExprNode Read FExprNode;
+    Property Dirty : Boolean Read FDirty;
+  public
+    Constructor Create(AOwner :TComponent); override;
+    Destructor Destroy; override;
+    Function IdentifierByName(AName : ShortString) : TFPExprIdentifierDef;
+    Procedure Clear;
+    Procedure EvaluateExpression(Var Result : TFPExpressionResult);
+    Function Evaluate : TFPExpressionResult;
+    Function ResultType : TResultType;
+    Property AsFloat : TExprFloat Read GetAsFloat;
+    Property AsInteger : Int64 Read GetAsInteger;
+    Property AsString : String Read GetAsString;
+    Property AsBoolean : Boolean Read GetAsBoolean;
+    Property AsDateTime : TDateTime Read GetAsDateTime;
+  Published
+    // The Expression to parse
+    property Expression : String read FExpression write SetExpression;
+    Property Identifiers : TFPExprIdentifierDefs Read FIdentifiers Write SetIdentifiers;
+    Property BuiltIns : TBuiltInCategories Read FBuiltIns Write SetBuiltIns;
+  end;
+
+  { TExprBuiltInManager }
+
+  TExprBuiltInManager = Class(TComponent)
+  Private
+    FDefs : TFPExprIdentifierDefs;
+    function GetCount: Integer;
+    function GetI(AIndex : Integer): TFPBuiltInExprIdentifierDef;
+  protected
+    Property Defs : TFPExprIdentifierDefs Read FDefs;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    Function IndexOfIdentifier(Const AName : ShortString) : Integer;
+    Function FindIdentifier(Const AName : ShortString) : TFPBuiltinExprIdentifierDef;
+    Function IdentifierByName(Const AName : ShortString) : TFPBuiltinExprIdentifierDef;
+    Function AddVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AResultType : TResultType; AValue : String) : TFPBuiltInExprIdentifierDef;
+    Function AddBooleanVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : Boolean) : TFPBuiltInExprIdentifierDef;
+    Function AddIntegerVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : Integer) : TFPBuiltInExprIdentifierDef;
+    Function AddFloatVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : TExprFloat) : TFPBuiltInExprIdentifierDef;
+    Function AddStringVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : String) : TFPBuiltInExprIdentifierDef;
+    Function AddDateTimeVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : TDateTime) : TFPBuiltInExprIdentifierDef;
+    Function AddFunction(Const ACategory : TBuiltInCategory; Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionCallBack) : TFPBuiltInExprIdentifierDef;
+    Function AddFunction(Const ACategory : TBuiltInCategory; Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionEvent) : TFPBuiltInExprIdentifierDef;
+    Property IdentifierCount : Integer Read GetCount;
+    Property Identifiers[AIndex : Integer] :TFPBuiltInExprIdentifierDef Read GetI;
+  end;
+
+  EExprParser = Class(Exception);
+
+
+Function TokenName (AToken : TTokenType) : String;
+Function ResultTypeName (AResult : TResultType) : String;
+Function CharToResultType(C : Char) : TResultType;
+Function BuiltinIdentifiers : TExprBuiltInManager;
+Procedure RegisterStdBuiltins(AManager : TExprBuiltInManager);
+
+Const
+  AllBuiltIns = [bcStrings,bcDateTime,bcMath,bcBoolean,bcConversion,bcData,bcVaria,bcUser];
+
+
+implementation
+
+uses typinfo;
+
+{ TFPExpressionParser }
+
+const
+  cNull=#0;
+  cSingleQuote = '''';
+
+  Digits        = ['0'..'9','.'];
+  WhiteSpace    = [' ',#13,#10,#9];
+  Operators     = ['+','-','<','>','=','/','*'];
+  Delimiters    = Operators+[',','(',')'];
+  Symbols       = ['%','^']+Delimiters;
+  WordDelimiters = WhiteSpace + Symbols;
+
+Resourcestring
+  SBadQuotes        = 'Unterminated string';
+  SUnknownDelimiter = 'Unknown delimiter character: "%s"';
+  SErrUnknownCharacter = 'Unknown character at pos %d: "%s"';
+  SErrUnexpectedEndOfExpression = 'Unexpected end of expression';
+  SErrUnknownComparison = 'Internal error: Unknown comparison';
+  SErrUnknownBooleanOp = 'Internal error: Unknown boolean operation';
+  SErrBracketExpected = 'Expected ) bracket at position %d, but got %s';
+  SerrUnknownTokenAtPos = 'Unknown token at pos %d : %s';
+  SErrLeftBracketExpected = 'Expected ( bracket at position %d, but got %s';
+  SErrInvalidFloat = '%s is not a valid floating-point value';
+  SErrUnknownIdentifier = 'Unknown identifier: %s';
+  SErrInExpression = 'Cannot evaluate: error in expression';
+  SErrInExpressionEmpty = 'Cannot evaluate: empty expression';
+  SErrCommaExpected =  'Expected comma (,) at position %d, but got %s';
+  SErrInvalidNumberChar = 'Unexpected character in number : %s';
+  SErrInvalidNumber = 'Invalid numerical value : %s';
+  SErrNoOperand = 'No operand for unary operation %s';
+  SErrNoleftOperand = 'No left operand for binary operation %s';
+  SErrNoRightOperand = 'No left operand for binary operation %s';
+  SErrNoNegation = 'Cannot negate expression of type %s : %s';
+  SErrNoNOTOperation = 'Cannot perform "not" on expression of type %s: %s';
+  SErrTypesDoNotMatch = 'Type mismatch: %s<>%s for expressions "%s" and "%s".';
+  SErrTypesIncompatible = 'Incompatible types: %s<>%s for expressions "%s" and "%s".';
+  SErrNoNodeToCheck = 'Internal error: No node to check !';
+  SInvalidNodeType = 'Node type (%s) not in allowed types (%s) for expression: %s';
+  SErrUnterminatedExpression = 'Badly terminated expression. Found token at position %d : %s';
+  SErrDuplicateIdentifier = 'An identifier with name "%s" already exists.';
+  SErrInvalidResultCharacter = '"%s" is not a valid return type indicator';
+  ErrInvalidArgumentCount = 'Invalid argument count for function %s';
+  SErrInvalidArgumentType = 'Invalid type for argument %d: Expected %s, got %s';
+  SErrInvalidResultType = 'Invalid result type: %s';
+  SErrNotVariable = 'Identifier %s is not a variable';
+  SErrInactive = 'Operation not allowed while an expression is active';
+  SErrIFNeedsBoolean = 'First argument to IF must be of type boolean: %s';
+  SErrCaseNeeds3 = 'Case statement needs to have at least 4 arguments';
+  SErrCaseEvenCount = 'Case statement needs to have an even number of arguments';
+  SErrCaseLabelNotAConst = 'Case label %d "%s" is not a constant expression';
+  SErrCaseLabelType = 'Case label %d "%s" needs type %s, but has type %s';
+  SErrCaseValueType = 'Case value %d "%s" needs type %s, but has type %s';
+
+{ ---------------------------------------------------------------------
+  Auxiliary functions
+  ---------------------------------------------------------------------}
+
+Procedure RaiseParserError(Msg : String);
+begin
+  Raise EExprParser.Create(Msg);
+end;
+
+Procedure RaiseParserError(Fmt : String; Args : Array of const);
+begin
+  Raise EExprParser.CreateFmt(Fmt,Args);
+end;
+
+Function TokenName (AToken : TTokenType) : String;
+
+begin
+  Result:=GetEnumName(TypeInfo(TTokenType),Ord(AToken));
+end;
+
+Function ResultTypeName (AResult : TResultType) : String;
+
+begin
+  Result:=GetEnumName(TypeInfo(TResultType),Ord(AResult));
+end;
+
+function CharToResultType(C: Char): TResultType;
+begin
+  Case Upcase(C) of
+    'S' : Result:=rtString;
+    'D' : Result:=rtDateTime;
+    'B' : Result:=rtBoolean;
+    'I' : Result:=rtInteger;
+    'F' : Result:=rtFloat;
+  else
+    RaiseParserError(SErrInvalidResultCharacter,[C]);
+  end;
+end;
+
+Var
+  BuiltIns : TExprBuiltInManager;
+
+Function BuiltinIdentifiers : TExprBuiltInManager;
+
+begin
+  If (BuiltIns=Nil) then
+    BuiltIns:=TExprBuiltInManager.Create(Nil);
+  Result:=BuiltIns;
+end;
+
+Procedure FreeBuiltIns;
+
+begin
+  FreeAndNil(Builtins);
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExpressionScanner
+  ---------------------------------------------------------------------}
+
+function TFPExpressionScanner.IsAlpha(C: Char): Boolean;
+begin
+  Result := C in ['A'..'Z', 'a'..'z'];
+end;
+
+constructor TFPExpressionScanner.Create;
+begin
+  Source:='';
+end;
+
+
+procedure TFPExpressionScanner.SetSource(const AValue: String);
+begin
+  FSource:=AValue;
+  LSource:=Length(FSource);
+  FTokenType:=ttEOF;
+  If LSource=0 then
+    FPos:=0
+  else
+    FPos:=1;
+  FChar:=Pchar(FSource);
+  FToken:='';
+end;
+
+function TFPExpressionScanner.NextPos: Char;
+begin
+  Inc(FPos);
+  Inc(FChar);
+  Result:=FChar^;
+end;
+
+
+function TFPExpressionScanner.IsWordDelim(C: Char): Boolean;
+begin
+  Result:=C in WordDelimiters;
+end;
+
+function TFPExpressionScanner.IsDelim(C: Char): Boolean;
+begin
+  Result:=C in Delimiters;
+end;
+
+function TFPExpressionScanner.IsDigit(C: Char): Boolean;
+begin
+  Result:=C in Digits;
+end;
+
+Procedure TFPExpressionScanner.SkipWhiteSpace;
+
+begin
+  While (FChar^ in WhiteSpace) and (FPos<=LSource) do
+    NextPos;
+end;
+
+Function TFPExpressionScanner.DoDelimiter : TTokenType;
+
+Var
+  B : Boolean;
+  C,D : Char;
+
+begin
+  C:=FChar^;
+  FToken:=C;
+  B:=C in ['<','>'];
+  D:=C;
+  C:=NextPos;
+
+  if B and (C in ['=','>']) then
+    begin
+    FToken:=FToken+C;
+    NextPos;
+    If (D='>') then
+      Result:=ttLargerThanEqual
+    else if (C='>') then
+      Result:=ttUnequal
+    else
+      Result:=ttLessThanEqual;
+    end
+  else
+    Case D of
+      '+' : Result := ttPlus;
+      '-' : Result := ttMinus;
+      '<' : Result := ttLessThan;
+      '>' : Result := ttLargerThan;
+      '=' : Result := ttEqual;
+      '/' : Result := ttDiv;
+      '*' : Result := ttMul;
+      '(' : Result := ttLeft;
+      ')' : Result := ttRight;
+      ',' : Result := ttComma;
+    else
+      ScanError(Format(SUnknownDelimiter,[D]));
+    end;
+
+end;
+
+Procedure TFPExpressionScanner.ScanError(Msg : String);
+
+begin
+  Raise EExprScanner.Create(Msg)
+end;
+
+Function TFPExpressionScanner.DoString : TTokenType;
+
+  Function TerminatingChar(C : Char) : boolean;
+
+  begin
+    Result:=(C=cNull) or
+            ((C=cSingleQuote) and
+              Not ((FPos<LSource) and (FSource[FPos+1]=cSingleQuote)));
+  end;
+
+
+Var
+  C : Char;
+
+begin
+  FToken := '';
+  C:=NextPos;
+  while not TerminatingChar(C) do
+    begin
+    FToken:=FToken+C;
+    If C=cSingleQuote then
+      NextPos;
+    C:=NextPos;
+    end;
+  if (C=cNull) then
+    ScanError(SBadQuotes);
+  Result := ttString;
+  FTokenType:=Result;
+  NextPos;
+end;
+
+function TFPExpressionScanner.GetCurrentChar: Char;
+begin
+  If FChar<>Nil then
+    Result:=FChar^
+  else
+    Result:=#0;
+end;
+
+Function TFPExpressionScanner.DoNumber : TTokenType;
+
+Var
+  C : Char;
+  X : TExprFloat;
+  I : Integer;
+
+begin
+  C:=CurrentChar;
+  while (not IsWordDelim(C)) and (C<>cNull) do
+    begin
+    If Not (IsDigit(C) or ((FToken<>'') and (Upcase(C)='E'))) then
+      ScanError(Format(SErrInvalidNumberChar,[C]));
+    FToken := FToken+C;
+    C:=NextPos;
+    end;
+  Val(FToken,X,I);
+  If (I<>0) then
+    ScanError(Format(SErrInvalidNumber,[FToken]));
+  Result:=ttNumber;
+end;
+
+Function TFPExpressionScanner.DoIdentifier : TTokenType;
+
+Var
+  C : Char;
+  S : String;
+begin
+  C:=CurrentChar;
+  while (not IsWordDelim(C)) and (C<>cNull) do
+    begin
+    FToken:=FToken+C;
+    C:=NextPos;
+    end;
+  S:=LowerCase(Token);
+  If (S='or') then
+    Result:=ttOr
+  else if (S='xor') then
+    Result:=ttXOr
+  else if (S='and') then
+    Result:=ttAnd
+  else if (S='true') then
+    Result:=ttTrue
+  else if (S='false') then
+    Result:=ttFalse
+  else if (S='not') then
+    Result:=ttnot
+  else if (S='if') then
+    Result:=ttif
+  else if (S='case') then
+    Result:=ttcase
+  else
+    Result:=ttIdentifier;
+end;
+
+Function TFPExpressionScanner.GetToken : TTokenType;
+
+Var
+  C : Char;
+
+begin
+  FToken := '';
+  SkipWhiteSpace;
+  C:=FChar^;
+  if c=cNull then
+    Result:=ttEOF
+  else if IsDelim(C) then
+    Result:=DoDelimiter
+  else if (C=cSingleQuote) then
+    Result:=DoString
+  else if IsDigit(C) then
+    Result:=DoNumber
+  else if IsAlpha(C) then
+    Result:=DoIdentifier
+  else
+    ScanError(Format(SErrUnknownCharacter,[FPos,C]))  ;
+  FTokenType:=Result;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExpressionParser
+  ---------------------------------------------------------------------}
+
+Function TFPExpressionParser.TokenType : TTokenType;
+
+begin
+  Result:=FScanner.TokenType;
+end;
+
+function TFPExpressionParser.CurrentToken: String;
+begin
+  Result:=FScanner.Token;
+end;
+
+procedure TFPExpressionParser.CreateHashList;
+
+Var
+  ID : TFPExpridentifierDef;
+  BID : TFPBuiltinExpridentifierDef;
+  I  : Integer;
+  M : TExprBuiltinManager;
+
+begin
+  FHashList.Clear;
+  // Builtins
+  M:=BuiltinsManager;
+  If (FBuiltins<>[]) and Assigned(M) then
+    For I:=0 to M.IdentifierCount-1 do
+      begin
+      BID:=M.Identifiers[I];
+      If BID.Category in FBuiltins then
+        FHashList.Add(LowerCase(BID.Name),BID);
+      end;
+  // User
+  For I:=0 to FIdentifiers.Count-1 do
+    begin
+    ID:=FIdentifiers[i];
+    FHashList.Add(LowerCase(ID.Name),ID);
+    end;
+  FDirty:=False;
+end;
+
+function TFPExpressionParser.IdentifierByName(AName: ShortString): TFPExprIdentifierDef;
+begin
+  If FDirty then
+    CreateHashList;
+  Result:=TFPExprIdentifierDef(FHashList.Find(LowerCase(AName)));
+end;
+
+procedure TFPExpressionParser.Clear;
+begin
+  FExpression:='';
+  FHashList.Clear;
+  FExprNode.Free;
+end;
+
+constructor TFPExpressionParser.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FIdentifiers:=TFPExprIdentifierDefs.Create(TFPExprIdentifierDef);
+  FIdentifiers.FParser:=Self;
+  FScanner:=TFPExpressionScanner.Create;
+  FHashList:=TFPHashObjectList.Create(False);
+end;
+
+destructor TFPExpressionParser.Destroy;
+begin
+  FreeAndNil(FHashList);
+  FreeAndNil(FExprNode);
+  FreeAndNil(FIdentifiers);
+  FreeAndNil(FScanner);
+  inherited Destroy;
+end;
+
+Function TFPExpressionParser.GetToken : TTokenType;
+
+begin
+  Result:=FScanner.GetToken;
+end;
+
+Procedure TFPExpressionParser.CheckEOF;
+
+begin
+  If (TokenType=ttEOF) then
+    ParserError(SErrUnexpectedEndOfExpression);
+end;
+
+procedure TFPExpressionParser.SetIdentifiers(const AValue: TFPExprIdentifierDefs
+  );
+begin
+  FIdentifiers.Assign(AValue)
+end;
+
+procedure TFPExpressionParser.EvaluateExpression(var Result: TFPExpressionResult);
+begin
+  If (FExpression='') then
+    ParserError(SErrInExpressionEmpty);
+  if not Assigned(FExprNode) then
+    ParserError(SErrInExpression);
+  FExprNode.GetNodeValue(Result);
+end;
+
+procedure TFPExpressionParser.ParserError(Msg: String);
+begin
+  Raise EExprParser.Create(Msg);
+end;
+
+function TFPExpressionParser.ConvertNode(Todo : TFPExprNode; ToType : TResultType): TFPExprNode;
+
+
+begin
+  Result:=ToDo;
+  Case ToDo.NodeType of
+    rtInteger :
+      Case ToType of
+        rtFloat    : Result:=TIntToFloatNode.Create(Result);
+        rtDateTime : Result:=TIntToDateTimeNode.Create(Result);
+      end;
+    rtFloat :
+      Case ToType of
+        rtDateTime : Result:=TFloatToDateTimeNode.Create(Result);
+      end;
+  end;
+end;
+
+function TFPExpressionParser.GetAsBoolean: Boolean;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtBoolean);
+  Result:=Res.ResBoolean;
+end;
+
+function TFPExpressionParser.GetAsDateTime: TDateTime;
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtDateTime);
+  Result:=Res.ResDatetime;
+end;
+
+function TFPExpressionParser.GetAsFloat: TExprFloat;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtFloat);
+  Result:=Res.ResFloat;
+end;
+
+function TFPExpressionParser.GetAsInteger: Int64;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtInteger);
+  Result:=Res.ResInteger;
+end;
+
+function TFPExpressionParser.GetAsString: String;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtString);
+  Result:=Res.ResString;
+end;
+
+{
+  Checks types of todo and match. If ToDO can be converted to it matches
+  the type of match, then a node is inserted.
+  For binary operations, this function is called for both operands.
+}
+
+function TFPExpressionParser.MatchNodes(Todo,Match : TFPExprNode): TFPExprNode;
+
+Var
+  TT,MT : TResultType;
+
+begin
+  Result:=Todo;
+  TT:=Todo.NodeType;
+  MT:=Match.NodeType;
+  If (TT<>MT) then
+    begin
+    if (TT=rtInteger) then
+      begin
+      if (MT in [rtFloat,rtDateTime]) then
+        Result:=ConvertNode(Todo,MT);
+      end
+    else if (TT=rtFloat) then
+      begin
+      if (MT=rtDateTime) then
+        Result:=ConvertNode(Todo,rtDateTime);
+      end;
+    end;
+end;
+
+{
+  if the result types differ, they are converted to a common type if possible.
+}
+
+Procedure TFPExpressionParser.CheckNodes(Var Left,Right : TFPExprNode);
+
+begin
+  Left:=MatchNodes(Left,Right);
+  Right:=MatchNodes(Right,Left);
+end;
+
+procedure TFPExpressionParser.SetBuiltIns(const AValue: TBuiltInCategories);
+begin
+  if FBuiltIns=AValue then exit;
+  FBuiltIns:=AValue;
+  FDirty:=True;
+end;
+
+Function TFPExpressionParser.Level1 : TFPExprNode;
+
+var
+  tt: TTokenType;
+  Right : TFPExprNode;
+
+begin
+{$ifdef debugexpr}Writeln('Level 1 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  if TokenType = ttNot then
+    begin
+    GetToken;
+    CheckEOF;
+    Right:=Level2;
+    Result:=TFPNotNode.Create(Right);
+    end
+  else
+    Result:=Level2;
+  Try
+    while (TokenType in [ttAnd,ttOr,ttXor]) do
+      begin
+      tt:=TokenType;
+      GetToken;
+      CheckEOF;
+      Right:=Level2;
+      Case tt of
+        ttOr  : Result:=TFPBinaryOrOperation.Create(Result,Right);
+        ttAnd : Result:=TFPBinaryAndOperation.Create(Result,Right);
+        ttXor : Result:=TFPBinaryXorOperation.Create(Result,Right);
+      Else
+        ParserError(SErrUnknownBooleanOp)
+      end;
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+function TFPExpressionParser.Level2: TFPExprNode;
+
+var
+  Right : TFPExprNode;
+  tt : TTokenType;
+  C : TFPBinaryOperationClass;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 2 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  Result:=Level3;
+  try
+    if (TokenType in ttComparisons) then
+      begin
+      tt:=TokenType;
+      GetToken;
+      CheckEOF;
+      Right:=Level3;
+      CheckNodes(Result,Right);
+      Case tt of
+        ttLessthan         : C:=TFPLessThanOperation;
+        ttLessthanEqual    : C:=TFPLessThanEqualOperation;
+        ttLargerThan       : C:=TFPGreaterThanOperation;
+        ttLargerThanEqual  : C:=TFPGreaterThanEqualOperation;
+        ttEqual            : C:=TFPEqualOperation;
+        ttUnequal          : C:=TFPUnequalOperation;
+      Else
+        ParserError(SErrUnknownComparison)
+      end;
+      Result:=C.Create(Result,Right);
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+function TFPExpressionParser.Level3: TFPExprNode;
+
+var
+  tt : TTokenType;
+  right : TFPExprNode;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 3 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  Result:=Level4;
+  try
+    while TokenType in [ttPlus,ttMinus] do
+      begin
+      tt:=TokenType;
+      GetToken;
+      CheckEOF;
+      Right:=Level4;
+      CheckNodes(Result,Right);
+      Case tt of
+        ttPlus  : Result:=TFPAddOperation.Create(Result,Right);
+        ttMinus : Result:=TFPSubtractOperation.Create(Result,Right);
+      end;
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+
+
+function TFPExpressionParser.Level4: TFPExprNode;
+
+var
+  tt : TTokenType;
+  right : TFPExprNode;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 4 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  Result:=Level5;
+  try
+    while (TokenType in [ttMul,ttDiv]) do
+      begin
+      tt:=TokenType;
+      GetToken;
+      Right:=Level5;
+      CheckNodes(Result,Right);
+      Case tt of
+        ttMul : Result:=TFPMultiplyOperation.Create(Result,Right);
+        ttDiv : Result:=TFPDivideOperation.Create(Result,Right);
+      end;
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+function TFPExpressionParser.Level5: TFPExprNode;
+
+Var
+  B : Boolean;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 5 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  B:=False;
+  if (TokenType in [ttPlus,ttMinus]) then
+    begin
+    B:=TokenType=ttMinus;
+    GetToken;
+    end;
+  Result:=Level6;
+  If B then
+    Result:=TFPNegateOperation.Create(Result);
+end;
+
+function TFPExpressionParser.Level6: TFPExprNode;
+begin
+{$ifdef debugexpr}  Writeln('Level 6 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  if (TokenType=ttLeft) then
+    begin
+    GetToken;
+    Result:=Level1;
+    try
+      if (TokenType<>ttRight) then
+        ParserError(Format(SErrBracketExpected,[SCanner.Pos,CurrentToken]));
+      GetToken;
+    Except
+      Result.Free;
+      Raise;
+    end;
+    end
+  else
+    Result:=Primitive;
+end;
+
+function TFPExpressionParser.Primitive: TFPExprNode;
+
+Var
+  I : Int64;
+  C : Integer;
+  X : TExprFloat;
+  ACount : Integer;
+  IFF : Boolean;
+  IFC : Boolean;
+  ID : TFPExprIdentifierDef;
+  Args : TExprArgumentArray;
+  AI : Integer;
+
+begin
+{$ifdef debugexpr}  Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  SetLength(Args,0);
+  if (TokenType=ttNumber) then
+    begin
+    if TryStrToInt64(CurrentToken,I) then
+      Result:=TFPConstExpression.CreateInteger(I)
+    else
+      begin
+      Val(CurrentToken,X,C);
+      If (I=0) then
+        Result:=TFPConstExpression.CreateFloat(X)
+      else
+        ParserError(Format(SErrInvalidFloat,[CurrentToken]));
+      end;
+    end
+  else if (TokenType=ttString) then
+    Result:=TFPConstExpression.CreateString(CurrentToken)
+  else if (TokenType in [ttTrue,ttFalse]) then
+    Result:=TFPConstExpression.CreateBoolean(TokenType=ttTrue)
+  else if Not (TokenType in [ttIdentifier,ttIf,ttcase]) then
+    ParserError(Format(SerrUnknownTokenAtPos,[Scanner.Pos,CurrentToken]))
+  else
+    begin
+    IFF:=TokenType=ttIf;
+    IFC:=TokenType=ttCase;
+    if Not (IFF or IFC) then
+      begin
+      ID:=self.IdentifierByName(CurrentToken);
+      If (ID=Nil) then
+        ParserError(Format(SErrUnknownIdentifier,[CurrentToken]))
+      end;
+    // Determine number of arguments
+    if Iff then
+      ACount:=3
+    else if IfC then
+      ACount:=-4
+    else if (ID.IdentifierType in [itFunctionCallBack,itFunctionHandler]) then
+      ACount:=ID.ArgumentCount
+    else
+      ACount:=0;
+    // Parse arguments.
+    // Negative is for variable number of arguments, where Abs(value) is the minimum number of arguments
+    If (ACount<>0) then
+      begin
+      GetToken;
+      If (TokenType<>ttLeft) then
+         ParserError(Format(SErrLeftBracketExpected,[Scanner.Pos,CurrentToken]));
+      SetLength(Args,Abs(ACount));
+      AI:=0;
+      Try
+        Repeat
+          GetToken;
+          // Check if we must enlarge the argument array
+          If (ACount<0) and (AI=Length(Args)) then
+            begin
+            SetLength(Args,AI+1);
+            Args[AI]:=Nil;
+            end;
+          Args[AI]:=Level1;
+          Inc(AI);
+          If (TokenType<>ttComma) then
+            If (AI<Abs(ACount)) then
+              ParserError(Format(SErrCommaExpected,[Scanner.Pos,CurrentToken]))
+        Until (AI=ACount) or ((ACount<0) and (TokenType=ttRight));
+        If TokenType<>ttRight then
+          ParserError(Format(SErrBracketExpected,[Scanner.Pos,CurrentToken]));
+      except
+        On E : Exception do
+          begin
+          Dec(AI);
+          While (AI>=0) do
+            begin
+            FreeAndNil(Args[Ai]);
+            Dec(AI);
+            end;
+          Raise;
+          end;
+      end;
+      end;
+    If Iff then
+      Result:=TIfOperation.Create(Args[0],Args[1],Args[2])
+    else If IfC then
+      Result:=TCaseOperation.Create(Args)
+    else
+      Case ID.IdentifierType of
+        itVariable         : Result:= TFPExprVariable.CreateIdentifier(ID);
+        itFunctionCallBack : Result:= TFPFunctionCallback.CreateFunction(ID,Args);
+        itFunctionHandler  : Result:= TFPFunctionEventHandler.CreateFunction(ID,Args);
+      end;
+    end;
+  GetToken;
+end;
+
+
+procedure TFPExpressionParser.SetExpression(const AValue: String);
+begin
+  if FExpression=AValue then exit;
+  FExpression:=AValue;
+  FScanner.Source:=AValue;
+  If Assigned(FExprNode) then
+    FreeAndNil(FExprNode);
+  If (FExpression<>'') then
+    begin
+    GetToken;
+    FExprNode:=Level1;
+    If (TokenType<>ttEOF) then
+      ParserError(Format(SErrUnterminatedExpression,[Scanner.Pos,CurrentToken]));
+    FExprNode.Check;
+    end
+  else
+    FExprNode:=Nil;
+end;
+
+procedure TFPExpressionParser.CheckResultType(const Res: TFPExpressionResult;
+  AType: TResultType); inline;
+begin
+  If (Res.ResultType<>AType) then
+    RaiseParserError(SErrInvalidResultType,[ResultTypeName(Res.ResultType)]);
+end;
+
+class function TFPExpressionParser.BuiltinsManager: TExprBuiltInManager;
+begin
+  Result:=BuiltinIdentifiers;
+end;
+
+
+function TFPExpressionParser.Evaluate: TFPExpressionResult;
+begin
+  EvaluateExpression(Result);
+end;
+
+function TFPExpressionParser.ResultType: TResultType;
+begin
+  if not Assigned(FExprNode) then
+    ParserError(SErrInExpression);
+  Result:=FExprNode.NodeType;;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExprIdentifierDefs
+  ---------------------------------------------------------------------}
+
+function TFPExprIdentifierDefs.GetI(AIndex : Integer): TFPExprIdentifierDef;
+begin
+  Result:=TFPExprIdentifierDef(Items[AIndex]);
+end;
+
+procedure TFPExprIdentifierDefs.SetI(AIndex : Integer;
+  const AValue: TFPExprIdentifierDef);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+procedure TFPExprIdentifierDefs.Update(Item: TCollectionItem);
+begin
+  If Assigned(FParser) then
+    FParser.FDirty:=True;
+end;
+
+function TFPExprIdentifierDefs.IndexOfIdentifier(const AName: ShortString
+  ): Integer;
+begin
+  Result:=Count-1;
+  While (Result>=0) And (CompareText(GetI(Result).Name,AName)<>0) do
+    Dec(Result);
+end;
+
+function TFPExprIdentifierDefs.FindIdentifier(const AName: ShortString
+  ): TFPExprIdentifierDef;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfIdentifier(AName);
+  If (I=-1) then
+    Result:=Nil
+  else
+    Result:=GetI(I);
+end;
+
+function TFPExprIdentifierDefs.IdentifierByName(const AName: ShortString
+  ): TFPExprIdentifierDef;
+begin
+  Result:=FindIdentifier(AName);
+  if (Result=Nil) then
+    RaiseParserError(SErrUnknownIdentifier,[AName]);
+end;
+
+function TFPExprIdentifierDefs.AddVariable(Const AName: ShortString;
+  AResultType: TResultType; AValue: String): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=AResultType;
+  Result.Value:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddBooleanVariable(Const AName: ShortString; AValue: Boolean
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtBoolean;
+  Result.FValue.ResBoolean:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddIntegerVariable(Const AName: ShortString; AValue: Integer
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtInteger;
+  Result.FValue.ResInteger:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddFloatVariable(Const AName: ShortString; AValue: TExprFloat
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtFloat;
+  Result.FValue.ResFloat:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddStringVariable(Const AName: ShortString; AValue: String
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtString;
+  Result.FValue.ResString:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddDateTimeVariable(Const AName: ShortString; AValue: TDateTime
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtDateTime;
+  Result.FValue.ResDateTime:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddFunction(const AName: ShortString;
+  const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionCallBack): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.Name:=Aname;
+  Result.IdentifierType:=itFunctionCallBack;
+  Result.ParameterTypes:=AParamTypes;
+  Result.ResultType:=CharToResultType(AResultType);
+  Result.FOnGetValueCB:=ACallBack;
+end;
+
+function TFPExprIdentifierDefs.AddFunction(const AName: ShortString;
+  const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionEvent): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.Name:=Aname;
+  Result.IdentifierType:=itFunctionHandler;
+  Result.ParameterTypes:=AParamTypes;
+  Result.ResultType:=CharToResultType(AResultType);
+  Result.FOnGetValue:=ACallBack;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExprIdentifierDef
+  ---------------------------------------------------------------------}
+
+procedure TFPExprIdentifierDef.SetName(const AValue: ShortString);
+begin
+  if FName=AValue then exit;
+  If (AValue<>'') then
+    If Assigned(Collection) and (TFPExprIdentifierDefs(Collection).IndexOfIdentifier(AValue)<>-1) then
+      RaiseParserError(SErrDuplicateIdentifier,[AValue]);
+  FName:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetResultType(const AValue: TResultType);
+
+begin
+  If AValue<>FValue.ResultType then
+    begin
+    FValue.ResultType:=AValue;
+    SetValue(FStringValue);
+    end;
+end;
+
+procedure TFPExprIdentifierDef.SetValue(const AValue: String);
+begin
+  FStringValue:=AValue;
+  If (AValue<>'') then
+    Case FValue.ResultType of
+      rtBoolean  : FValue.ResBoolean:=FStringValue='True';
+      rtInteger  : FValue.ResInteger:=StrToInt(AValue);
+      rtFloat    : FValue.ResFloat:=StrToFloat(AValue);
+      rtDateTime : FValue.ResDateTime:=StrToDateTime(AValue);
+      rtString   : FValue.ResString:=AValue;
+    end
+  else
+    Case FValue.ResultType of
+      rtBoolean  : FValue.ResBoolean:=False;
+      rtInteger  : FValue.ResInteger:=0;
+      rtFloat    : FValue.ResFloat:=0.0;
+      rtDateTime : FValue.ResDateTime:=0;
+      rtString   : FValue.ResString:='';
+    end
+end;
+
+procedure TFPExprIdentifierDef.CheckResultType(const AType: TResultType);
+begin
+  If FValue.ResultType<>AType then
+    RaiseParserError(SErrInvalidResultType,[ResultTypeName(AType)])
+end;
+
+procedure TFPExprIdentifierDef.CheckVariable;
+begin
+  If Identifiertype<>itvariable then
+    RaiseParserError(SErrNotVariable,[Name]);
+end;
+
+function TFPExprIdentifierDef.ArgumentCount: Integer;
+begin
+  Result:=Length(FArgumentTypes);
+end;
+
+procedure TFPExprIdentifierDef.Assign(Source: TPersistent);
+
+Var
+  EID : TFPExprIdentifierDef;
+
+begin
+  if (Source is TFPExprIdentifierDef) then
+    begin
+    EID:=Source as TFPExprIdentifierDef;
+    FStringValue:=EID.FStringValue;
+    FValue:=EID.FValue;
+    FArgumentTypes:=EID.FArgumentTypes;
+    FIDType:=EID.FIDType;
+    FName:=EID.FName;
+    FOnGetValue:=EID.FOnGetValue;
+    FOnGetValueCB:=EID.FOnGetValueCB;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+procedure TFPExprIdentifierDef.SetArgumentTypes(const AValue: String);
+
+Var
+  I : integer;
+
+begin
+  if FArgumentTypes=AValue then exit;
+  For I:=1 to Length(AValue) do
+    CharToResultType(AValue[i]);
+  FArgumentTypes:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsBoolean(const AValue: Boolean);
+begin
+  CheckVariable;
+  CheckResultType(rtBoolean);
+  FValue.ResBoolean:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsDateTime(const AValue: TDateTime);
+begin
+  CheckVariable;
+  CheckResultType(rtDateTime);
+  FValue.ResDateTime:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsFloat(const AValue: TExprFloat);
+begin
+  CheckVariable;
+  CheckResultType(rtFloat);
+  FValue.ResFloat:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsInteger(const AValue: Int64);
+begin
+  CheckVariable;
+  CheckResultType(rtInteger);
+  FValue.ResInteger:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsString(const AValue: String);
+begin
+  CheckVariable;
+  CheckResultType(rtString);
+  FValue.resString:=AValue;
+end;
+
+function TFPExprIdentifierDef.GetValue: String;
+begin
+  Case FValue.ResultType of
+    rtBoolean  : If FValue.ResBoolean then
+                   Result:='True'
+                 else
+                   Result:='False';
+    rtInteger  : Result:=IntToStr(FValue.ResInteger);
+    rtFloat    : Result:=FloatToStr(FValue.ResFloat);
+    rtDateTime : Result:=FormatDateTime('cccc',FValue.ResDateTime);
+    rtString   : Result:=FValue.ResString;
+  end;
+end;
+
+function TFPExprIdentifierDef.GetResultType: TResultType;
+begin
+  Result:=FValue.ResultType;
+end;
+
+function TFPExprIdentifierDef.GetAsFloat: TExprFloat;
+begin
+  CheckResultType(rtFloat);
+  CheckVariable;
+  Result:=FValue.ResFloat;
+end;
+
+function TFPExprIdentifierDef.GetAsBoolean: Boolean;
+begin
+  CheckResultType(rtBoolean);
+  CheckVariable;
+  Result:=FValue.ResBoolean;
+end;
+
+function TFPExprIdentifierDef.GetAsDateTime: TDateTime;
+begin
+  CheckResultType(rtDateTime);
+  CheckVariable;
+  Result:=FValue.ResDateTime;
+end;
+
+function TFPExprIdentifierDef.GetAsInteger: Int64;
+begin
+  CheckResultType(rtInteger);
+  CheckVariable;
+  Result:=FValue.ResInteger;
+end;
+
+function TFPExprIdentifierDef.GetAsString: String;
+begin
+  CheckResultType(rtString);
+  CheckVariable;
+  Result:=FValue.ResString;
+end;
+
+{ ---------------------------------------------------------------------
+  TExprBuiltInManager
+  ---------------------------------------------------------------------}
+
+function TExprBuiltInManager.GetCount: Integer;
+begin
+  Result:=FDefs.Count;
+end;
+
+function TExprBuiltInManager.GetI(AIndex : Integer
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs[Aindex])
+end;
+
+constructor TExprBuiltInManager.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDefs:=TFPExprIdentifierDefs.Create(TFPBuiltInExprIdentifierDef)
+end;
+
+destructor TExprBuiltInManager.Destroy;
+begin
+  FreeAndNil(FDefs);
+  inherited Destroy;
+end;
+
+function TExprBuiltInManager.IndexOfIdentifier(const AName: ShortString
+  ): Integer;
+begin
+  Result:=FDefs.IndexOfIdentifier(AName);
+end;
+
+function TExprBuiltInManager.FindIdentifier(const AName: ShortString
+  ): TFPBuiltinExprIdentifierDef;
+begin
+  Result:=TFPBuiltinExprIdentifierDef(FDefs.FindIdentifier(AName));
+end;
+
+function TExprBuiltInManager.IdentifierByName(const AName: ShortString
+  ): TFPBuiltinExprIdentifierDef;
+begin
+  Result:=TFPBuiltinExprIdentifierDef(FDefs.IdentifierByName(AName));
+end;
+
+function TExprBuiltInManager.AddVariable(const ACategory: TBuiltInCategory;
+  const AName: ShortString; AResultType: TResultType; AValue: String
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.Addvariable(AName,AResultType,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddBooleanVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: Boolean
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddBooleanvariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddIntegerVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: Integer
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddIntegerVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFloatVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString;
+  AValue: TExprFloat): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFloatVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddStringVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: String
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddStringVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddDateTimeVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: TDateTime
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddDateTimeVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFunction(const ACategory: TBuiltInCategory;
+  const AName: ShortString; const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionCallBack): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFunction(AName,AResultType,AParamTypes,ACallBack));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFunction(const ACategory: TBuiltInCategory;
+  const AName: ShortString; const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionEvent): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFunction(AName,AResultType,AParamTypes,ACallBack));
+  Result.Category:=ACategory;
+end;
+
+
+{ ---------------------------------------------------------------------
+  Various Nodes
+  ---------------------------------------------------------------------}
+
+{ TFPBinaryOperation }
+
+procedure TFPBinaryOperation.CheckSameNodeTypes;
+
+Var
+  LT,RT : TResultType;
+
+
+begin
+  LT:=Left.NodeType;
+  RT:=Right.NodeType;
+  if (RT<>LT) then
+    RaiseParserError(SErrTypesDoNotMatch,[ResultTypeName(LT),ResultTypeName(RT),Left.AsString,Right.AsString])
+end;
+
+constructor TFPBinaryOperation.Create(ALeft, ARight: TFPExprNode);
+begin
+  FLeft:=ALeft;
+  FRight:=ARight;
+end;
+
+destructor TFPBinaryOperation.Destroy;
+begin
+  FreeAndNil(FLeft);
+  FreeAndNil(FRight);
+  inherited Destroy;
+end;
+
+procedure TFPBinaryOperation.Check;
+begin
+  If Not Assigned(Left) then
+    RaiseParserError(SErrNoLeftOperand,[classname]);
+  If Not Assigned(Right) then
+    RaiseParserError(SErrNoRightOperand,[classname]);
+end;
+
+{ TFPUnaryOperator }
+
+constructor TFPUnaryOperator.Create(AOperand: TFPExprNode);
+begin
+  FOperand:=AOperand;
+end;
+
+destructor TFPUnaryOperator.Destroy;
+begin
+  FreeAndNil(FOperand);
+  inherited Destroy;
+end;
+
+procedure TFPUnaryOperator.Check;
+begin
+  If Not Assigned(Operand) then
+    RaiseParserError(SErrNoOperand,[Self.className]);
+end;
+
+{ TFPConstExpression }
+
+constructor TFPConstExpression.CreateString(AValue: String);
+begin
+  FValue.ResultType:=rtString;
+  FValue.ResString:=AValue;
+end;
+
+constructor TFPConstExpression.CreateInteger(AValue: Int64);
+begin
+  FValue.ResultType:=rtInteger;
+  FValue.ResInteger:=AValue;
+end;
+
+constructor TFPConstExpression.CreateDateTime(AValue: TDateTime);
+begin
+  FValue.ResultType:=rtDateTime;
+  FValue.ResDateTime:=AValue;
+end;
+
+constructor TFPConstExpression.CreateFloat(AValue: TExprFloat);
+begin
+  Inherited create;
+  FValue.ResultType:=rtFloat;
+  FValue.ResFloat:=AValue;
+end;
+
+constructor TFPConstExpression.CreateBoolean(AValue: Boolean);
+begin
+  FValue.ResultType:=rtBoolean;
+  FValue.ResBoolean:=AValue;
+end;
+
+procedure TFPConstExpression.Check;
+begin
+  // Nothing to check;
+end;
+
+function TFPConstExpression.NodeType: TResultType;
+begin
+  Result:=FValue.ResultType;
+end;
+
+Procedure TFPConstExpression.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Result:=FValue;
+end;
+
+function TFPConstExpression.AsString: string ;
+begin
+  Case NodeType of
+    rtString  : Result:=''''+FValue.resString+'''';
+    rtInteger : Result:=IntToStr(FValue.resInteger);
+    rtDateTime : Result:=''''+FormatDateTime('cccc',FValue.resDateTime)+'''';
+    rtBoolean : If FValue.ResBoolean then Result:='True' else Result:='False';
+    rtFloat : Str(FValue.ResFloat,Result);
+  end;
+end;
+
+
+{ TFPNegateOperation }
+
+procedure TFPNegateOperation.Check;
+begin
+  Inherited;
+  If Not (Operand.NodeType in [rtInteger,rtFloat]) then
+    RaiseParserError(SErrNoNegation,[ResultTypeName(Operand.NodeType),Operand.AsString])
+end;
+
+function TFPNegateOperation.NodeType: TResultType;
+begin
+  Result:=Operand.NodeType;
+end;
+
+Procedure TFPNegateOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Case Result.ResultType of
+    rtInteger : Result.resInteger:=-Result.ResInteger;
+    rtFloat : Result.resFloat:=-Result.ResFloat;
+  end;
+end;
+
+function TFPNegateOperation.AsString: String;
+begin
+  Result:='-'+TrimLeft(Operand.AsString);
+end;
+
+{ TFPBinaryAndOperation }
+
+procedure TFPBooleanOperation.Check;
+begin
+  inherited Check;
+  CheckNodeType(Left,[rtInteger,rtBoolean]);
+  CheckNodeType(Right,[rtInteger,rtBoolean]);
+  CheckSameNodeTypes;
+end;
+
+function TFPBooleanOperation.NodeType: TResultType;
+begin
+  Result:=Left.NodeType;
+end;
+
+Procedure TFPBinaryAndOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean : Result.resBoolean:=Result.ResBoolean and RRes.ResBoolean;
+    rtInteger : Result.resInteger:=Result.ResInteger and RRes.ResInteger;
+  end;
+end;
+
+function TFPBinaryAndOperation.AsString: string;
+begin
+  Result:=Left.AsString+' and '+Right.AsString;
+end;
+
+{ TFPExprNode }
+
+procedure TFPExprNode.CheckNodeType(Anode: TFPExprNode; Allowed: TResultTypes);
+
+Var
+  S : String;
+  A : TResultType;
+
+begin
+  If (Anode=Nil) then
+    RaiseParserError(SErrNoNodeToCheck);
+  If Not (ANode.NodeType in Allowed) then
+    begin
+    S:='';
+    For A:=Low(TResultType) to High(TResultType) do
+      If A in Allowed then
+        begin
+        If S<>'' then
+          S:=S+',';
+        S:=S+ResultTypeName(A);
+        end;
+    RaiseParserError(SInvalidNodeType,[ResultTypeName(ANode.NodeType),S,ANode.AsString]);
+    end;
+end;
+
+function TFPExprNode.NodeValue: TFPExpressionResult;
+begin
+  GetNodeValue(Result);
+end;
+
+{ TFPBinaryOrOperation }
+
+function TFPBinaryOrOperation.AsString: string;
+begin
+  Result:=Left.AsString+' or '+Right.AsString;
+end;
+
+Procedure TFPBinaryOrOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean : Result.resBoolean:=Result.ResBoolean or RRes.ResBoolean;
+    rtInteger : Result.resInteger:=Result.ResInteger or RRes.ResInteger;
+  end;
+end;
+
+{ TFPBinaryXOrOperation }
+
+function TFPBinaryXOrOperation.AsString: string;
+begin
+  Result:=Left.AsString+' xor '+Right.AsString;
+end;
+
+Procedure TFPBinaryXOrOperation.GetNodeValue(var Result : TFPExpressionResult);
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean : Result.resBoolean:=Result.ResBoolean xor RRes.ResBoolean;
+    rtInteger : Result.resInteger:=Result.ResInteger xor RRes.ResInteger;
+  end;
+end;
+
+{ TFPNotNode }
+
+procedure TFPNotNode.Check;
+begin
+  If Not (Operand.NodeType in [rtInteger,rtBoolean]) then
+    RaiseParserError(SErrNoNotOperation,[ResultTypeName(Operand.NodeType),Operand.AsString])
+end;
+
+function TFPNotNode.NodeType: TResultType;
+begin
+  Result:=Operand.NodeType;
+end;
+
+procedure TFPNotNode.GetNodeValue(var Result: TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Case result.ResultType of
+    rtInteger : Result.resInteger:=Not Result.resInteger;
+    rtBoolean : Result.resBoolean:=Not Result.resBoolean;
+  end
+end;
+
+function TFPNotNode.AsString: String;
+begin
+  Result:='not '+Operand.AsString;
+end;
+
+{ TIfOperation }
+
+constructor TIfOperation.Create(ACondition, ALeft, ARight: TFPExprNode);
+begin
+  Inherited Create(ALeft,ARight);
+  FCondition:=ACondition;
+end;
+
+destructor TIfOperation.destroy;
+begin
+  FreeAndNil(FCondition);
+  inherited destroy;
+end;
+
+procedure TIfOperation.GetNodeValue(var Result: TFPExpressionResult);
+
+begin
+  FCondition.GetNodeValue(Result);
+  If Result.ResBoolean then
+    Left.GetNodeValue(Result)
+  else
+    Right.GetNodeValue(Result)
+end;
+
+procedure TIfOperation.Check;
+begin
+  inherited Check;
+  if (Condition.NodeType<>rtBoolean) then
+    RaiseParserError(SErrIFNeedsBoolean,[Condition.AsString]);
+  CheckSameNodeTypes;
+end;
+
+function TIfOperation.NodeType: TResultType;
+begin
+  Result:=Left.NodeType;
+end;
+
+function TIfOperation.AsString: string;
+begin
+  Result:=Format('if(%s , %s , %s)',[Condition.AsString,Left.AsString,Right.AsString]);
+end;
+
+{ TCaseOperation }
+
+procedure TCaseOperation.GetNodeValue(var Result: TFPExpressionResult);
+
+Var
+  I,L : Integer;
+  B : Boolean;
+  RT,RV : TFPExpressionResult;
+
+begin
+  FArgs[0].GetNodeValue(RT);
+  L:=Length(FArgs);
+  I:=2;
+  B:=False;
+  While (Not B) and (I<L) do
+    begin
+    FArgs[i].GetNodeValue(RV);
+    Case RT.ResultType of
+      rtBoolean  : B:=RT.ResBoolean=RV.ResBoolean;
+      rtInteger  : B:=RT.ResInteger=RV.ResInteger;
+      rtFloat    : B:=RT.ResFloat=RV.ResFLoat;
+      rtDateTime : B:=RT.ResDateTime=RV.ResDateTime;
+      rtString   : B:=RT.ResString=RV.ResString;
+    end;
+    If Not B then
+      Inc(I,2);
+    end;
+  // Set result type.
+  Result.ResultType:=FArgs[1].NodeType;
+  If B then
+    FArgs[I+1].GetNodeValue(Result)
+  else if ((L mod 2)=0) then
+    FArgs[1].GetNodeValue(Result);
+end;
+
+procedure TCaseOperation.Check;
+
+Var
+  T,V : TResultType;
+  I : Integer;
+  N : TFPExprNode;
+
+begin
+  If (Length(FArgs)<3) then
+    RaiseParserError(SErrCaseNeeds3);
+  If ((Length(FArgs) mod 2)=1) then
+    RaiseParserError(SErrCaseEvenCount);
+  T:=FArgs[0].NodeType;
+  V:=FArgs[1].NodeType;
+  For I:=2 to Length(Fargs)-1 do
+    begin
+    N:=FArgs[I];
+    // Even argument types (labels) must equal tag.
+    If ((I mod 2)=0) then
+      begin
+      If Not (N is TFPConstExpression) then
+        RaiseParserError(SErrCaseLabelNotAConst,[I div 2,N.AsString]);
+      If (N.NodeType<>T) then
+        RaiseParserError(SErrCaseLabelType,[I div 2,N.AsString,ResultTypeName(T),ResultTypeName(N.NodeType)]);
+      end
+    else // Odd argument types (values) must match first.
+      begin
+      If (N.NodeType<>V) then
+        RaiseParserError(SErrCaseValueType,[(I-1)div 2,N.AsString,ResultTypeName(V),ResultTypeName(N.NodeType)]);
+      end
+    end;
+end;
+
+function TCaseOperation.NodeType: TResultType;
+begin
+  Result:=FArgs[1].NodeType;
+end;
+
+constructor TCaseOperation.Create(Args: TExprArgumentArray);
+begin
+  Fargs:=Args;
+end;
+
+destructor TCaseOperation.destroy;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Length(FArgs)-1 do
+    FreeAndNil(Fargs[I]);
+  inherited destroy;
+end;
+
+function TCaseOperation.AsString: string;
+
+Var
+  I : integer;
+
+begin
+  Result:='';
+  For I:=0 to Length(FArgs)-1 do
+    begin
+    If (Result<>'') then
+      Result:=Result+', ';
+    Result:=Result+FArgs[i].AsString;
+    end;
+  Result:='Case('+Result+')';
+end;
+
+{ TFPBooleanResultOperation }
+
+procedure TFPBooleanResultOperation.Check;
+begin
+  inherited Check;
+  CheckSameNodeTypes;
+end;
+
+function TFPBooleanResultOperation.NodeType: TResultType;
+begin
+  Result:=rtBoolean;
+end;
+
+{ TFPEqualOperation }
+
+function TFPEqualOperation.AsString: string;
+begin
+  Result:=Left.AsString+' = '+Right.AsString;
+end;
+
+Procedure TFPEqualOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean  : Result.resBoolean:=Result.ResBoolean=RRes.ResBoolean;
+    rtInteger  : Result.resBoolean:=Result.ResInteger=RRes.ResInteger;
+    rtFloat    : Result.resBoolean:=Result.ResFloat=RRes.ResFLoat;
+    rtDateTime : Result.resBoolean:=Result.ResDateTime=RRes.ResDateTime;
+    rtString   : Result.resBoolean:=Result.ResString=RRes.ResString;
+  end;
+  Result.ResultType:=rtBoolean;
+end;
+
+{ TFPUnequalOperation }
+
+function TFPUnequalOperation.AsString: string;
+begin
+  Result:=Left.AsString+' <> '+Right.AsString;
+end;
+
+Procedure TFPUnequalOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Inherited GetNodeValue(Result);
+  Result.ResBoolean:=Not Result.ResBoolean;
+end;
+
+
+{ TFPLessThanOperation }
+
+function TFPLessThanOperation.AsString: string;
+begin
+  Result:=Left.AsString+' < '+Right.AsString;
+end;
+
+procedure TFPLessThanOperation.GetNodeValue(var Result : TFPExpressionResult);
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtInteger  : Result.resBoolean:=Result.ResInteger<RRes.ResInteger;
+    rtFloat    : Result.resBoolean:=Result.ResFloat<RRes.ResFLoat;
+    rtDateTime : Result.resBoolean:=Result.ResDateTime<RRes.ResDateTime;
+    rtString   : Result.resBoolean:=Result.ResString<RRes.ResString;
+  end;
+  Result.ResultType:=rtBoolean;
+end;
+
+{ TFPGreaterThanOperation }
+
+function TFPGreaterThanOperation.AsString: string;
+begin
+  Result:=Left.AsString+' > '+Right.AsString;
+end;
+
+Procedure TFPGreaterThanOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtInteger : case Right.NodeType of
+                  rtInteger : Result.resBoolean:=Result.ResInteger>RRes.ResInteger;
+                  rtFloat : Result.resBoolean:=Result.ResInteger>RRes.ResFloat;
+                end;
+    rtFloat   : case Right.NodeType of
+                  rtInteger : Result.resBoolean:=Result.ResFloat>RRes.ResInteger;
+                  rtFloat : Result.resBoolean:=Result.ResFloat>RRes.ResFLoat;
+                end;
+    rtDateTime : Result.resBoolean:=Result.ResDateTime>RRes.ResDateTime;
+    rtString   : Result.resBoolean:=Result.ResString>RRes.ResString;
+  end;
+  Result.ResultType:=rtBoolean;
+end;
+
+{ TFPGreaterThanEqualOperation }
+
+function TFPGreaterThanEqualOperation.AsString: string;
+begin
+  Result:=Left.AsString+' >= '+Right.AsString;
+end;
+
+Procedure TFPGreaterThanEqualOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Inherited GetNodeValue(Result);
+  Result.ResBoolean:=Not Result.ResBoolean;
+end;
+
+{ TFPLessThanEqualOperation }
+
+function TFPLessThanEqualOperation.AsString: string;
+begin
+  Result:=Left.AsString+' <= '+Right.AsString;
+end;
+
+Procedure TFPLessThanEqualOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Inherited GetNodeValue(Result);
+  Result.ResBoolean:=Not Result.ResBoolean;
+end;
+
+{ TFPOrderingOperation }
+
+procedure TFPOrderingOperation.Check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat,rtDateTime,rtString];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  inherited Check;
+end;
+
+{ TMathOperation }
+
+procedure TMathOperation.Check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat,rtDateTime,rtString];
+
+begin
+  inherited Check;
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  CheckSameNodeTypes;
+end;
+
+function TMathOperation.NodeType: TResultType;
+begin
+  Result:=Left.NodeType;
+end;
+
+{ TFPAddOperation }
+
+function TFPAddOperation.AsString: string;
+begin
+  Result:=Left.AsString+' + '+Right.asString;
+end;
+
+Procedure TFPAddOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResInteger:=Result.ResInteger+RRes.ResInteger;
+    rtString   : Result.ResString:=Result.ResString+RRes.ResString;
+    rtDateTime : Result.ResDateTime:=Result.ResDateTime+RRes.ResDateTime;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat+RRes.ResFLoat;
+  end;
+  Result.ResultType:=NodeType;
+end;
+
+{ TFPSubtractOperation }
+
+procedure TFPSubtractOperation.check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat,rtDateTime];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  inherited check;
+end;
+
+function TFPSubtractOperation.AsString: string;
+begin
+  Result:=Left.AsString+' - '+Right.asString;
+end;
+
+Procedure TFPSubtractOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResInteger:=Result.ResInteger-RRes.ResInteger;
+    rtDateTime : Result.ResDateTime:=Result.ResDateTime-RRes.ResDateTime;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat-RRes.ResFLoat;
+  end;
+end;
+
+{ TFPMultiplyOperation }
+
+procedure TFPMultiplyOperation.check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  Inherited;
+end;
+
+function TFPMultiplyOperation.AsString: string;
+begin
+  Result:=Left.AsString+' * '+Right.asString;
+end;
+
+Procedure TFPMultiplyOperation.GetNodeValue(var Result : TFPExpressionResult);
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResInteger:=Result.ResInteger*RRes.ResInteger;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat*RRes.ResFLoat;
+  end;
+end;
+
+{ TFPDivideOperation }
+
+procedure TFPDivideOperation.check;
+Const
+  AllowedTypes =[rtInteger,rtfloat];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  inherited check;
+end;
+
+function TFPDivideOperation.AsString: string;
+begin
+  Result:=Left.AsString+' / '+Right.asString;
+end;
+
+function TFPDivideOperation.NodeType: TResultType;
+begin
+  Result:=rtFLoat;
+end;
+
+Procedure TFPDivideOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResFloat:=Result.ResInteger/RRes.ResInteger;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat/RRes.ResFLoat;
+  end;
+  Result.ResultType:=rtFloat;
+end;
+
+{ TFPConvertNode }
+
+function TFPConvertNode.AsString: String;
+begin
+  Result:=Operand.AsString;
+end;
+
+{ TIntToFloatNode }
+
+procedure TIntConvertNode.Check;
+begin
+  inherited Check;
+  CheckNodeType(Operand,[rtInteger])
+end;
+
+function TIntToFloatNode.NodeType: TResultType;
+begin
+  Result:=rtFloat;
+end;
+
+Procedure TIntToFloatNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Result.ResFloat:=Result.ResInteger;
+  Result.ResultType:=rtFloat;
+end;
+
+
+{ TIntToDateTimeNode }
+
+function TIntToDateTimeNode.NodeType: TResultType;
+begin
+  Result:=rtDatetime;
+end;
+
+procedure TIntToDateTimeNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetnodeValue(Result);
+  Result.ResDateTime:=Result.ResInteger;
+  Result.ResultType:=rtDateTime;
+end;
+
+{ TFloatToDateTimeNode }
+
+procedure TFloatToDateTimeNode.Check;
+begin
+  inherited Check;
+  CheckNodeType(Operand,[rtFloat]);
+end;
+
+function TFloatToDateTimeNode.NodeType: TResultType;
+begin
+  Result:=rtDateTime;
+end;
+
+Procedure TFloatToDateTimeNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Result.ResDateTime:=Result.ResFloat;
+  Result.ResultType:=rtDateTime;
+end;
+
+{ TFPExprIdentifierNode }
+
+constructor TFPExprIdentifierNode.CreateIdentifier(AID: TFPExprIdentifierDef);
+begin
+  Inherited Create;
+  FID:=AID;
+  PResult:[email protected];
+  FResultType:=FID.ResultType;
+end;
+
+function TFPExprIdentifierNode.NodeType: TResultType;
+begin
+  Result:=FResultType;
+end;
+
+Procedure TFPExprIdentifierNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Result:=PResult^;
+  Result.ResultType:=FResultType;
+end;
+
+{ TFPExprVariable }
+
+procedure TFPExprVariable.Check;
+begin
+  // Do nothing;
+end;
+
+function TFPExprVariable.AsString: string;
+begin
+  Result:=FID.Name;
+end;
+
+{ TFPExprFunction }
+
+procedure TFPExprFunction.CalcParams;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Length(FArgumentParams)-1 do
+    FArgumentNodes[i].GetNodeValue(FArgumentParams[i]);
+end;
+
+procedure TFPExprFunction.Check;
+
+Var
+  I : Integer;
+  rtp,rta : TResultType;
+
+begin
+  If Length(FArgumentNodes)<>FID.ArgumentCount then
+    RaiseParserError(ErrInvalidArgumentCount,[FID.Name]);
+  For I:=0 to Length(FArgumentNodes)-1 do
+    begin
+    rtp:=CharToResultType(FID.ParameterTypes[i+1]);
+    rta:=FArgumentNodes[i].NodeType;
+    If (rtp<>rta) then
+      RaiseParserError(SErrInvalidArgumentType,[I+1,ResultTypeName(rtp),ResultTypeName(rta)])
+    end;
+end;
+
+constructor TFPExprFunction.CreateFunction(AID: TFPExprIdentifierDef;
+  const Args: TExprArgumentArray);
+begin
+  Inherited CreateIdentifier(AID);
+  FArgumentNodes:=Args;
+  SetLength(FArgumentParams,Length(Args));
+end;
+
+destructor TFPExprFunction.Destroy;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Length(FArgumentNodes)-1 do
+    FreeAndNil(FArgumentNodes[I]);
+  inherited Destroy;
+end;
+
+function TFPExprFunction.AsString: String;
+
+Var
+  S : String;
+  I : Integer;
+
+begin
+  S:='';
+  For I:=0 to length(FArgumentNodes)-1 do
+    begin
+    If (S<>'') then
+      S:=S+',';
+    S:=S+FArgumentNodes[I].AsString;
+    end;
+  If (S<>'') then
+    S:='('+S+')';
+  Result:=FID.Name+S;
+end;
+
+{ TFPFunctionCallBack }
+
+constructor TFPFunctionCallBack.CreateFunction(AID: TFPExprIdentifierDef;
+  Const Args : TExprArgumentArray);
+begin
+  Inherited;
+  FCallBack:=AID.OnGetFunctionValueCallBack;
+end;
+
+Procedure TFPFunctionCallBack.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  If Length(FArgumentParams)>0 then
+    CalcParams;
+  FCallBack(Result,FArgumentParams);
+  Result.ResultType:=NodeType;
+end;
+
+{ TFPFunctionEventHandler }
+
+constructor TFPFunctionEventHandler.CreateFunction(AID: TFPExprIdentifierDef;
+  Const Args : TExprArgumentArray);
+begin
+  Inherited;
+  FCallBack:=AID.OnGetFunctionValue;
+end;
+
+Procedure TFPFunctionEventHandler.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  If Length(FArgumentParams)>0 then
+    CalcParams;
+  FCallBack(Result,FArgumentParams);
+  Result.ResultType:=NodeType;
+end;
+
+{ ---------------------------------------------------------------------
+  Standard Builtins support
+  ---------------------------------------------------------------------}
+
+{ Template for builtin.
+
+Procedure MyCallback (Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+end;
+
+}
+
+// Math builtins
+
+Procedure BuiltInCos(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Cos(Args[0].resFloat);
+end;
+
+Procedure BuiltInSin(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Sin(Args[0].resFloat);
+end;
+
+Procedure BuiltInArcTan(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Arctan(Args[0].resFloat);
+end;
+
+Procedure BuiltInAbs(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Abs(Args[0].resFloat);
+end;
+
+Procedure BuiltInSqr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Sqr(Args[0].resFloat);
+end;
+
+Procedure BuiltInSqrt(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Sqrt(Args[0].resFloat);
+end;
+
+Procedure BuiltInExp(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Exp(Args[0].resFloat);
+end;
+
+Procedure BuiltInLn(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Ln(Args[0].resFloat);
+end;
+
+Const
+  L10 = ln(10);
+
+Procedure BuiltInLog(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Ln(Args[0].resFloat)/L10;
+end;
+
+Procedure BuiltInRound(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Round(Args[0].resFloat);
+end;
+
+Procedure BuiltInTrunc(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Trunc(Args[0].resFloat);
+end;
+
+Procedure BuiltInInt(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Int(Args[0].resFloat);
+end;
+
+Procedure BuiltInFrac(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=frac(Args[0].resFloat);
+end;
+
+// String builtins
+
+Procedure BuiltInLength(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Length(Args[0].resString);
+end;
+
+Procedure BuiltInCopy(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Copy(Args[0].resString,Args[1].resInteger,Args[2].resInteger);
+end;
+
+Procedure BuiltInDelete(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Args[0].resString;
+  Delete(Result.resString,Args[1].resInteger,Args[2].resInteger);
+end;
+
+Procedure BuiltInPos(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Pos(Args[0].resString,Args[1].resString);
+end;
+
+Procedure BuiltInUppercase(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Uppercase(Args[0].resString);
+end;
+
+Procedure BuiltInLowercase(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Lowercase(Args[0].resString);
+end;
+
+Procedure BuiltInStringReplace(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  F : TReplaceFlags;
+
+begin
+  F:=[];
+  If Args[3].resBoolean then
+    Include(F,rfReplaceAll);
+  If Args[4].resBoolean then
+    Include(F,rfIgnoreCase);
+  Result.resString:=StringReplace(Args[0].resString,Args[1].resString,Args[2].resString,f);
+end;
+
+Procedure BuiltInCompareText(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=CompareText(Args[0].resString,Args[1].resString);
+end;
+
+// Date/Time builtins
+
+Procedure BuiltInDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resDateTime:=Date;
+end;
+
+Procedure BuiltInTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resDateTime:=Time;
+end;
+
+Procedure BuiltInNow(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resDateTime:=Now;
+end;
+
+Procedure BuiltInDayofWeek(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=DayOfWeek(Args[0].resDateTime);
+end;
+
+Procedure BuiltInExtractYear(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Args[0].resDateTime,Y,M,D);
+  Result.resInteger:=Y;
+end;
+
+Procedure BuiltInExtractMonth(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Args[0].resDateTime,Y,M,D);
+  Result.resInteger:=M;
+end;
+
+Procedure BuiltInExtractDay(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Args[0].resDateTime,Y,M,D);
+  Result.resInteger:=D;
+end;
+
+Procedure BuiltInExtractHour(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=H;
+end;
+
+Procedure BuiltInExtractMin(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=M;
+end;
+
+Procedure BuiltInExtractSec(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=S;
+end;
+
+Procedure BuiltInExtractMSec(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=MS;
+end;
+
+Procedure BuiltInEncodedate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=Encodedate(Args[0].resInteger,Args[1].resInteger,Args[2].resInteger);
+end;
+
+Procedure BuiltInEncodeTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=EncodeTime(Args[0].resInteger,Args[1].resInteger,Args[2].resInteger,Args[3].resInteger);
+end;
+
+Procedure BuiltInEncodeDateTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=EncodeDate(Args[0].resInteger,Args[1].resInteger,Args[2].resInteger)
+                     +EncodeTime(Args[3].resInteger,Args[4].resInteger,Args[5].resInteger,Args[6].resInteger);
+end;
+
+Procedure BuiltInShortDayName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=ShortDayNames[Args[0].resInteger];
+end;
+
+Procedure BuiltInShortMonthName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=ShortMonthNames[Args[0].resInteger];
+end;
+Procedure BuiltInLongDayName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=LongDayNames[Args[0].resInteger];
+end;
+
+Procedure BuiltInLongMonthName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=LongMonthNames[Args[0].resInteger];
+end;
+
+Procedure BuiltInFormatDateTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=FormatDateTime(Args[0].resString,Args[1].resDateTime);
+end;
+
+
+// Conversion
+Procedure BuiltInIntToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=IntToStr(Args[0].resinteger);
+end;
+
+Procedure BuiltInStrToInt(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=StrToInt(Args[0].resString);
+end;
+
+Procedure BuiltInStrToIntDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=StrToIntDef(Args[0].resString,Args[1].resInteger);
+end;
+
+Procedure BuiltInFloatToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=FloatToStr(Args[0].resFloat);
+end;
+
+Procedure BuiltInStrToFloat(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resFloat:=StrToFloat(Args[0].resString);
+end;
+
+Procedure BuiltInStrToFloatDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resFloat:=StrToFloatDef(Args[0].resString,Args[1].resFloat);
+end;
+
+Procedure BuiltInDateToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=DateToStr(Args[0].resDateTime);
+end;
+
+Procedure BuiltInTimeToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=TimeToStr(Args[0].resDateTime);
+end;
+
+Procedure BuiltInStrToDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDate(Args[0].resString);
+end;
+
+Procedure BuiltInStrToDateDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDateDef(Args[0].resString,Args[1].resDateTime);
+end;
+
+Procedure BuiltInStrToTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToTime(Args[0].resString);
+end;
+
+Procedure BuiltInStrToTimeDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToTimeDef(Args[0].resString,Args[1].resDateTime);
+end;
+
+Procedure BuiltInStrToDateTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDateTime(Args[0].resString);
+end;
+
+Procedure BuiltInStrToDateTimeDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDateTimeDef(Args[0].resString,Args[1].resDateTime);
+end;
+
+Procedure BuiltInBoolToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=BoolToStr(Args[0].resBoolean);
+end;
+
+Procedure BuiltInStrToBool(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resBoolean:=StrToBool(Args[0].resString);
+end;
+
+Procedure BuiltInStrToBoolDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resBoolean:=StrToBoolDef(Args[0].resString,Args[1].resBoolean);
+end;
+
+// Boolean
+Procedure BuiltInShl(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=Args[0].resInteger shl Args[1].resInteger
+end;
+
+Procedure BuiltInShr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=Args[0].resInteger shr Args[1].resInteger
+end;
+
+Procedure BuiltinIFS(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resString:=Args[1].resString
+  else
+    Result.resString:=Args[2].resString
+end;
+
+Procedure BuiltinIFI(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resinteger:=Args[1].resinteger
+  else
+    Result.resinteger:=Args[2].resinteger
+end;
+
+Procedure BuiltinIFF(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resfloat:=Args[1].resfloat
+  else
+    Result.resfloat:=Args[2].resfloat
+end;
+
+Procedure BuiltinIFD(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resDateTime:=Args[1].resDateTime
+  else
+    Result.resDateTime:=Args[2].resDateTime
+end;
+
+Procedure RegisterStdBuiltins(AManager : TExprBuiltInManager);
+
+begin
+  With AManager do
+    begin
+    AddFloatVariable(bcMath,'pi',Pi);
+    // Math functions
+    AddFunction(bcMath,'cos','F','F',@BuiltinCos);
+    AddFunction(bcMath,'sin','F','F',@BuiltinSin);
+    AddFunction(bcMath,'arctan','F','F',@BuiltinArctan);
+    AddFunction(bcMath,'abs','F','F',@BuiltinAbs);
+    AddFunction(bcMath,'sqr','F','F',@BuiltinSqr);
+    AddFunction(bcMath,'sqrt','F','F',@BuiltinSqrt);
+    AddFunction(bcMath,'exp','F','F',@BuiltinExp);
+    AddFunction(bcMath,'ln','F','F',@BuiltinLn);
+    AddFunction(bcMath,'log','F','F',@BuiltinLog);
+    AddFunction(bcMath,'frac','F','F',@BuiltinFrac);
+    AddFunction(bcMath,'int','F','F',@BuiltinInt);
+    AddFunction(bcMath,'round','I','F',@BuiltinRound);
+    AddFunction(bcMath,'trunc','I','F',@BuiltinTrunc);
+    // String
+    AddFunction(bcStrings,'length','I','S',@BuiltinLength);
+    AddFunction(bcStrings,'copy','S','SII',@BuiltinCopy);
+    AddFunction(bcStrings,'delete','S','SII',@BuiltinDelete);
+    AddFunction(bcStrings,'pos','I','SS',@BuiltinPos);
+    AddFunction(bcStrings,'lowercase','S','S',@BuiltinLowercase);
+    AddFunction(bcStrings,'uppercase','S','S',@BuiltinUppercase);
+    AddFunction(bcStrings,'stringreplace','S','SSSBB',@BuiltinStringReplace);
+    AddFunction(bcStrings,'comparetext','I','SS',@BuiltinCompareText);
+    // Date/Time
+    AddFunction(bcDateTime,'date','D','',@BuiltinDate);
+    AddFunction(bcDateTime,'time','D','',@BuiltinTime);
+    AddFunction(bcDateTime,'now','D','',@BuiltinNow);
+    AddFunction(bcDateTime,'dayofweek','I','D',@BuiltinDayofweek);
+    AddFunction(bcDateTime,'extractyear','I','D',@BuiltinExtractYear);
+    AddFunction(bcDateTime,'extractmonth','I','D',@BuiltinExtractMonth);
+    AddFunction(bcDateTime,'extractday','I','D',@BuiltinExtractDay);
+    AddFunction(bcDateTime,'extracthour','I','D',@BuiltinExtractHour);
+    AddFunction(bcDateTime,'extractmin','I','D',@BuiltinExtractMin);
+    AddFunction(bcDateTime,'extractsec','I','D',@BuiltinExtractSec);
+    AddFunction(bcDateTime,'extractmsec','I','D',@BuiltinExtractMSec);
+    AddFunction(bcDateTime,'encodedate','D','III',@BuiltinEncodedate);
+    AddFunction(bcDateTime,'encodetime','D','IIII',@BuiltinEncodeTime);
+    AddFunction(bcDateTime,'encodedatetime','D','IIIIIII',@BuiltinEncodeDateTime);
+    AddFunction(bcDateTime,'shortdayname','S','I',@BuiltinShortDayName);
+    AddFunction(bcDateTime,'shortmonthname','S','I',@BuiltinShortMonthName);
+    AddFunction(bcDateTime,'longdayname','S','I',@BuiltinLongDayName);
+    AddFunction(bcDateTime,'longmonthname','S','I',@BuiltinLongMonthName);
+    AddFunction(bcDateTime,'formatdatetime','S','SD',@BuiltinFormatDateTime);
+    // Boolean
+    AddFunction(bcBoolean,'shl','I','II',@BuiltinShl);
+    AddFunction(bcBoolean,'shr','I','II',@BuiltinShr);
+    AddFunction(bcBoolean,'IFS','S','BSS',@BuiltinIFS);
+    AddFunction(bcBoolean,'IFF','F','BFF',@BuiltinIFF);
+    AddFunction(bcBoolean,'IFD','D','BDD',@BuiltinIFD);
+    AddFunction(bcBoolean,'IFI','I','BII',@BuiltinIFI);
+    // Conversion
+    AddFunction(bcConversion,'inttostr','S','I',@BuiltInIntToStr);
+    AddFunction(bcConversion,'strtoint','I','S',@BuiltInStrToInt);
+    AddFunction(bcConversion,'strtointdef','I','SI',@BuiltInStrToIntDef);
+    AddFunction(bcConversion,'floattostr','S','F',@BuiltInFloatToStr);
+    AddFunction(bcConversion,'strtofloat','F','S',@BuiltInStrToFloat);
+    AddFunction(bcConversion,'strtofloatdef','F','SF',@BuiltInStrToFloatDef);
+    AddFunction(bcConversion,'booltostr','S','B',@BuiltInBoolToStr);
+    AddFunction(bcConversion,'strtobool','B','S',@BuiltInStrToBool);
+    AddFunction(bcConversion,'strtobooldef','B','SB',@BuiltInStrToBoolDef);
+    AddFunction(bcConversion,'datetostr','S','D',@BuiltInDateToStr);
+    AddFunction(bcConversion,'timetostr','S','D',@BuiltInTimeToStr);
+    AddFunction(bcConversion,'strtodate','D','S',@BuiltInStrToDate);
+    AddFunction(bcConversion,'strtodatedef','D','SD',@BuiltInStrToDateDef);
+    AddFunction(bcConversion,'strtotime','D','S',@BuiltInStrToTime);
+    AddFunction(bcConversion,'strtotimedef','D','SD',@BuiltInStrToTimeDef);
+    AddFunction(bcConversion,'strtodatetime','D','S',@BuiltInStrToDateTime);
+    AddFunction(bcConversion,'strtodatetimedef','D','SD',@BuiltInStrToDateTimeDef);
+    end;
+end;
+
+{ TFPBuiltInExprIdentifierDef }
+
+procedure TFPBuiltInExprIdentifierDef.Assign(Source: TPersistent);
+begin
+  inherited Assign(Source);
+  If Source is TFPBuiltInExprIdentifierDef then
+    FCategory:=(Source as TFPBuiltInExprIdentifierDef).Category;
+end;
+
+initialization
+  RegisterStdBuiltins(BuiltinIdentifiers);
+
+finalization
+  FreeBuiltins;
+end.

+ 3 - 1
packages/fcl-base/src/inifiles.pp

@@ -620,6 +620,8 @@ constructor TIniFile.Create(const AFileName: string; AEscapeLineFeeds : Boolean
 var
   slLines: TStringList;
 begin
+  If Not (self is TMemIniFile) then
+    StripQuotes:=True;
   inherited Create(AFileName,AEscapeLineFeeds);
   FStream := nil;
   slLines := TStringList.Create;
@@ -730,7 +732,7 @@ begin
                // Joost, 2-jan-2007: The check (J>1) is there for the case that
                // the value consist of a single double-quote character. (see
                // mantis bug 6555)
-               If (J>1) and (sValue[1]='"') and (sValue[J]='"') then
+               If (J>1) and ((sValue[1] in ['"','''']) and (sValue[J]=sValue[1])) then
                  sValue:=Copy(sValue,2,J-2);
                end;  
            end;

+ 3 - 0
packages/fcl-base/src/win/daemonapp.inc

@@ -199,8 +199,11 @@ begin
   If (Pos(' ',E)<>0) then
     E:='"'+E+'"';
   E:=E+' --run'; // Add --run argument;
+  If (DD.RunArguments<>'') then
+    E:=E+' '+DD.RunArguments;
   N:=DD.Name;
   DN:=DD.DisplayName;
+  
   With DD.WinBindings do
     begin
     LG:=GroupName;

+ 168 - 23
packages/fcl-db/src/datadict/fpdatadict.pp

@@ -231,6 +231,7 @@ Type
     FTableName: String;
     function GetOnProgress: TDDProgressEvent;
     function GetPrimaryKeyName: String;
+    function GetPrimaryIndexDef : TDDIndexDef;
     procedure SetTableName(const AValue: String);
   protected
     function GetSectionName: String; override;
@@ -245,10 +246,13 @@ Type
     Function AddField(AFieldName : String = '') : TDDFieldDef;
     Procedure SaveToIni(Ini: TCustomInifile; ASection : String); override;
     Procedure LoadFromIni(Ini: TCustomInifile; ASection : String); override;
+    procedure PrimaryIndexToFields;
+    procedure FieldsToPrimaryIndex;
     Property Fields : TDDFieldDefs Read FFieldDefs;
     Property Indexes : TDDIndexDefs Read FIndexDefs;
     Property ForeignKeys : TDDForeignKeyDefs Read FKeyDefs;
     Property OnProgress : TDDProgressEvent Read GetOnProgress;
+    Property PrimaryIndexDef : TDDIndexDef read GetPrimaryIndexDef;
   Published
     Property TableName : String Read FTableName Write SetTableName;
     Property PrimaryKeyConstraintName : String Read GetPrimaryKeyName Write FPrimaryKeyName;
@@ -545,17 +549,22 @@ Type
   Public
     Destructor Destroy; override;
     Function GetConnectString : String; virtual;
-    Function ImportTables(Tables : TDDTableDefs; List : TStrings; UpdateExisting : Boolean) : Integer;
     // Mandatory for all data dictionary engines.
     Class function Description : string; virtual; abstract;
     Class function DBType : String; virtual; abstract;
     Class function EngineCapabilities : TFPDDEngineCapabilities; virtual;
     Function Connect(const ConnectString : String) : Boolean; virtual; abstract;
     Procedure Disconnect ; virtual; abstract;
+    procedure ImportDatadict (Adatadict: TFPDataDictionary; UpdateExisting : Boolean);
     Function GetTableList(List : TStrings) : Integer; virtual; abstract;
+    Function ImportTables(Tables : TDDTableDefs; List : TStrings; UpdateExisting : Boolean) : Integer;
     Function ImportFields(Table : TDDTableDef) : Integer; virtual; abstract;
-    Function ImportDomains(Domains : TDDDomainDefs) : Integer; virtual;
-    Function ImportSequences(Sequences : TDDSequenceDefs) : Integer; virtual;
+    Function ImportIndexes(Table : TDDTableDef) : Integer; virtual; abstract;
+    function GetDomainList(List: TSTrings) : integer; virtual;
+    Function ImportDomains(Domains : TDDDomainDefs; List : TStrings; UpdateExisting : boolean) : Integer; virtual;
+    function GetSequenceList (List:TStrings): integer; virtual;
+    Function ImportSequences(Sequences : TDDSequenceDefs; List : TStrings; UpdateExisting : boolean) : Integer; virtual;
+
     // Override depending on capabilities
     Procedure CreateTable(Table : TDDTableDef); virtual;
     // Should not open the dataset.
@@ -1311,8 +1320,31 @@ begin
 end;
 
 function TDDTableDef.GetPrimaryKeyName: String;
+var i : TDDIndexDef;
+begin
+  if FPrimaryKeyName <> '' then
+    Result := FPrimaryKeyName
+  else
+    begin
+    I := GetPrimaryIndexDef;
+    if assigned (I) then
+      Result := I.IndexName
+    else
+      Result:=Tablename+'_PK';
+    end;
+end;
+
+function TDDTableDef.GetPrimaryIndexDef: TDDIndexDef;
+var r : integer;
 begin
-  Result:=Tablename+'_PK';
+  r := Indexes.count;
+  repeat
+    dec (r);
+  until (r < 0) or (ixPrimary in Indexes[r].Options);
+  if r < 0 then
+    result := nil
+  else
+    result := Indexes[r];
 end;
 
 function TDDTableDef.GetOnProgress: TDDProgressEvent;
@@ -1444,6 +1476,56 @@ begin
   FIndexDefs.LoadFromIni(Ini,ASection+SIndexSuffix);
 end;
 
+procedure TDDTableDef.PrimaryIndexToFields;
+var I : TDDIndexDef;
+    r : integer;
+    l : TFPDDFieldList;
+begin
+  I := GetPrimaryIndexDef;
+  if assigned (I) then
+    begin
+    for r := 0 to Fields.count-1 do
+      Fields[r].ProviderFlags := Fields[r].ProviderFlags - [pfInKey];
+    l := TFPDDFieldList.create;
+    try
+      Fields.FillFieldList (I.Fields, l);
+      for r := 0 to l.count-1 do
+        l[r].ProviderFlags := l[r].ProviderFlags + [pfInKey];
+    finally
+      l.Free;
+    end;
+    end;
+end;
+
+procedure TDDTableDef.FieldsToPrimaryIndex;
+var I : TDDIndexDef;
+    r : integer;
+    s : string;
+begin
+  I := GetPrimaryIndexDef;
+  s := '';
+  for r := 0 to fields.count-1 do
+    if pfInKey in fields[r].ProviderFlags then
+      s := s + ';' + fields[r].FieldName;
+  if s = '' then
+    begin
+    if assigned (I) then
+      I.Free;
+    end
+  else
+    begin
+    s := copy(s, 2, maxint);
+    if assigned (I) then
+      I.Fields := s
+    else
+      begin
+      I := Indexes.AddIndex(GetPrimaryKeyName);
+      I.Fields := s;
+      I.Options := I.Options + [ixPrimary];
+      end;
+    end;
+end;
+
 { ---------------------------------------------------------------------
   TDDTableDefs
   ---------------------------------------------------------------------}
@@ -1774,18 +1856,26 @@ begin
     TD:=Nil;
     j:=Tables.IndexOfTable(List[i]);
     If (J=-1) then
-      TD:=Tables.AddTAble(List[i])
+      TD:=Tables.AddTable(List[i])
     else if UpdateExisting then
       TD:=Tables[J];
     If (TD<>nil) then
       begin
       DoProgress(Format(SDDImportingTable,[TD.TableName]));
       ImportFields(TD);
+      if ecTableIndexes in EngineCapabilities then
+        ImportIndexes(TD);
       Inc(Result);
       end
     end;
 end;
 
+function TFPDDEngine.GetDomainList(List: TSTrings): integer;
+begin
+  List.Clear;
+  result := 0;
+end;
+
 function TFPDDEngine.CreateSQLEngine: TFPDDSQLEngine;
 begin
   Result:=TFPDDSQLEngine.Create;
@@ -1796,14 +1886,68 @@ begin
   Result:=[];
 end;
 
-function TFPDDEngine.ImportDomains(Domains: TDDDomainDefs): Integer;
+procedure TFPDDEngine.ImportDatadict(Adatadict: TFPDatadictionary;
+  UpdateExisting: Boolean);
+var L : TStringList;
+    r : integer;
 begin
-  Domains.Clear;
+  l := TStringlist.Create;
+  try
+    if ecDomains in EngineCapabilities then
+      begin
+      GetDomainList (L);
+      if UpdateExisting then // Delete domains that don't exist anymore
+        begin
+        for r := ADatadict.Domains.count-1 downto 0 do
+          if L.indexOf(ADatadict.Domains[r].DomainName) < 0 then
+            ADatadict.Domains[r].Free;
+        end;
+      ImportDomains (ADatadict.Domains, L, UpdateExisting);
+      end;
+
+    L.Clear;
+    GetTableList (L);
+    if UpdateExisting then // delete tables that don't exist anymore
+      begin
+      for r := ADatadict.Tables.count-1 downto 0 do
+        if L.indexOf(ADatadict.Tables[r].TableName) < 0 then
+          ADatadict.Tables[r].Free;
+      end;
+    ImportTables (ADatadict.Tables, L, UpdateExisting);
+
+    if ecSequences in EngineCapabilities then
+      begin
+      L.Clear;
+      GetSequenceList (L);
+      if UpdateExisting then // Delete sequences that don't exist anymore
+        begin
+        for r := ADatadict.Sequences.count-1 downto 0 do
+          if L.indexOf(ADatadict.Sequences[r].SequenceName) < 0 then
+            ADatadict.Sequences[r].Free;
+        end;
+      ImportSequences (ADatadict.Sequences, L, UpdateExisting);
+      end;
+  finally
+    L.Free;
+  end;
+end;
+
+function TFPDDEngine.ImportDomains(Domains: TDDDomainDefs; List : TStrings; UpdateExisting : boolean) : Integer;
+begin
+  result := 0;
+  writeln ('importing no domains');
+end;
+
+function TFPDDEngine.GetSequenceList(List: TStrings): integer;
+begin
+  List.Clear;
+  result := 0;
 end;
 
-function TFPDDEngine.ImportSequences(Sequences: TDDSequenceDefs): Integer;
+function TFPDDEngine.ImportSequences(Sequences: TDDSequenceDefs; List : TStrings; UpdateExisting : boolean) : Integer;
 begin
-  Sequences.Clear;
+  result := 0;
+  writeln ('importing no sequences');
 end;
 
 procedure TFPDDEngine.CreateTable(Table: TDDTableDef);
@@ -1986,7 +2130,10 @@ end;
 function TFPDDSQLEngine.FieldTypeString(FD : TDDFieldDef) : String;
 
 begin
-  Result:=FieldTypeString(FD.FieldType,FD.Size,FD.Precision);
+  if FD.DomainName <> '' then
+    Result := FD.DomainName
+  else
+    Result:=FieldTypeString(FD.FieldType,FD.Size,FD.Precision);
 end;
 
 
@@ -2390,15 +2537,9 @@ begin
     KF:=TFPDDFieldlist.Create(False);
     try
       KF.OwnsObjects:=False;
-      I:=0;
-      While (I<TableDef.Indexes.Count) and (KF.Count=0) do
-        begin
-        ID:=TableDef.Indexes[i];
-        If (ixPrimary in ID.Options) then
-          TableDef.Fields.FillFieldList(ID.Fields,KF);
-        Inc(I);
-        end;
-      If (KF.Count=0) then
+      if assigned (TableDef.PrimaryIndexDef) then
+        TableDef.fields.FillFieldList(TableDef.PrimaryIndexDef.Fields, KF)
+      else
         For I:=0 to TableDef.Fields.Count-1 do
           begin
           FD:=TableDef.Fields[I];
@@ -2460,7 +2601,8 @@ Var
 
 begin
   For I:=0 to Indexes.Count-1 do
-    SQL.Add(CreateIndexSQL(Indexes[i])+TerminatorChar);
+    if not (ixPrimary in Indexes[i].Options) then
+      SQL.Add(CreateIndexSQL(Indexes[i])+TerminatorChar);
 end;
 
 procedure TFPDDSQLEngine.CreateSequencesSQLStrings(Sequences: TFPDDSequenceList;
@@ -2822,9 +2964,10 @@ end;
 function TDDDomainDefs.IndexOfDomain(ADomainName: String): Integer;
 
 begin
-  Result:=Count-1;
-  While (Result>=0) and (CompareText(GetDomain(Result).DomainName,ADomainName)=0) do
+  Result := Count;
+  repeat
     Dec(Result);
+  until (Result < 0) or (CompareText(GetDomain(Result).DomainName,ADomainName) = 0);
 end;
 
 function TDDDomainDefs.FindDomain(ADomainName: String): TDDDomainDef;
@@ -3019,8 +3162,10 @@ end;
 
 function TDDSequenceDefs.IndexOfSequence(ASequenceName: String): Integer;
 begin
-  While (Result>=0) and (CompareText(GetSequence(Result).SequenceName,ASequenceName)=0) do
+  result := count;
+  repeat
     Dec(Result);
+  until (Result<0) or (CompareText(GetSequence(Result).SequenceName,ASequenceName)=0);
 end;
 
 function TDDSequenceDefs.FindSequence(ASequenceName: String): TDDSequenceDef;

+ 3 - 3
packages/fcl-db/src/datadict/fpdddiff.pp

@@ -11,7 +11,7 @@
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
- **********************************************************************}
+  **********************************************************************}
 unit fpdddiff;
 
 {$mode objfpc}{$H+}
@@ -188,8 +188,8 @@ begin
     FieldDifference(dtSurplus, nil, Targ)
   else if (Not FieldTypesEqual(Src,Targ))
           or (Src.required <> Targ.required)
-          or (Src.DomainName <> Targ.DomainName)
-          or (Src.DefaultExpression <> Targ.DefaultExpression)
+          or (comparetext(Src.DomainName, Targ.DomainName) <> 0)
+          or (comparetext(Src.DefaultExpression, Targ.DefaultExpression) <> 0)
           or ((Src.Size <> Targ.Size) and not (Src.Fieldtype in [ftBlob]))
           or (Src.Precision <> Targ.Precision) then
     FieldDifference(dtDifferent, Src, Targ)

+ 343 - 51
packages/fcl-db/src/datadict/fpddfb.pp

@@ -20,7 +20,7 @@ unit fpddfb;
 interface
 
 uses
-  Classes, SysUtils, sqldb, fpdatadict, fpddsqldb;
+  Classes, SysUtils, sqldb, fpdatadict, fpddsqldb, db;
   
 Type
 
@@ -35,12 +35,16 @@ Type
 
   TSQLDBFBDDEngine = Class(TSQLDBDDEngine)
   private
+    function ConvertFBFieldType(FDfieldtype, FBsubtype: integer): TFieldType;
   Protected
     Function CreateConnection(AConnectString  : String) : TSQLConnection; override;
   Public
-    Class function EngineCapabilities : TFPDDEngineCapabilities; virtual;
     function ImportFields(Table: TDDTableDef): Integer; override;
+    Function ImportIndexes(Table : TDDTableDef) : Integer; override;
+    Function ImportSequences(Sequences : TDDSequenceDefs; List : TStrings; UpdateExisting : boolean) : Integer; override;
+    Function ImportDomains(Domains : TDDDomainDefs; List : TStrings; UpdateExisting : boolean) : Integer; override;
     Function CreateSQLEngine : TFPDDSQLEngine; override;
+    Class function EngineCapabilities : TFPDDEngineCapabilities; override;
     Class function Description : string; override;
     Class function DBType : String; override;
   end;
@@ -54,7 +58,7 @@ Procedure UnRegisterFBDDEngine;
 
 implementation
 
-uses ibconnection, db;
+uses ibconnection;
 
 Procedure RegisterFBDDEngine;
 
@@ -92,6 +96,323 @@ begin
   Result:='Firebird/Interbase';
 end;
 
+function TSQLDBFBDDEngine.ConvertFBFieldType (FDfieldtype, FBsubtype : integer) : TFieldType;
+var b : byte;
+begin
+  if FDFieldType > 255 then
+    begin
+    if FDFieldType = 261 then
+      result := ftBlob       {BLOB}
+    else
+      result := ftUnknown;
+    end
+  else
+    begin
+    b := byte(FDFieldType and $FF);
+    if (b in [7,8,16]) and (FBsubtype <> 0) then
+      // BCD types: 1= Numeric, 2 := Decimal
+      result := ftBCD
+    else
+      case b of
+        14 : result := ftFixedChar; {CHAR}
+        37 : result := ftString;    {VARCHAR}
+        40 : result := ftString;    {CSTRING ?}
+        11 : result := ftFloat;     {D-FLOAT ?}
+        27 : result := ftFloat;     {DOUBLE}
+        10 : result := ftFloat;     {FLOAT}
+        16 : result := ftLargeint;  {INT64}
+        8  : result := ftInteger;   {INTEGER}
+        9  : result := ftlargeint;  {QUAD ?}
+        7  : result := ftSmallint;  {SMALLINT}
+        12 : result := ftDate;      {DATE dialect 3}
+        13 : result := ftTime;      {TIME}
+        35 : result := ftDateTime;  {TIMESTAMP dialect 3, DATE in dialect 1,2}
+        else result := ftUnknown;
+      end;
+    end;
+end;
+
+function TSQLDBFBDDEngine.ImportIndexes(Table: TDDTableDef): Integer;
+const
+  SQLindexes = 'SELECT '+
+        'I.RDB$INDEX_NAME as IndexName, '+
+        'I.RDB$INDEX_TYPE as IndexType, '+
+        'I.RDB$UNIQUE_FLAG as IsUnique, '+
+        'R.RDB$CONSTRAINT_TYPE as ConstraintType, '+
+        'R.RDB$CONSTRAINT_NAME as ConstraintName '+
+        'FROM '+
+        'RDB$INDICES I '+
+        'LEFT JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME = R.RDB$INDEX_NAME '+
+        'WHERE '+
+        'I.RDB$RELATION_NAME=''%s'' '+
+        'AND I.RDB$FOREIGN_KEY is null '+
+        'ORDER BY I.RDB$INDEX_NAME';
+
+  {
+  SQLchecks = 'SELECT '+
+        'R.RDB$CONSTRAINT_NAME as ConstraintName, '+
+        'R.RDB$CONSTRAINT_TYPE as ConstraintType, '+
+        'T.RDB$TRIGGER_SOURCE as CheckSource, '+
+        'FROM '+
+        'RDB$RELATION_CONSTRAINTS R '+
+        'LEFT JOIN RDB$CHECK_CONSTRAINTS C ON R.RDB$CONSTRAINT_NAME = C.RDB$CONSTRAINT_NAME '+
+        'LEFT JOIN RDB$TRIGGERS T ON T.RDB$TRIGGER_NAME = C.RDB$TRIGGER_NAME '+
+        'WHERE '+
+        'R.RDB$RELATION_NAME=''%s'' '+
+        'ORDER BY R.RDB$CONSTRAINT_NAME';
+
+  SQLforeign = 'SELECT '+
+        'R.RDB$CONSTRAINT_NAME as ConstraintName, '+
+        'R.RDB$INDEX_NAME as IndexName, '+
+        'E.RDB$CONST_NAME_UQ as RefUnique, '+
+        'E.RDB$UPDATE_RULE as OnUpdate, '+
+        'E.RDB$DELETE_RULE as OnDelete, '+
+        'I.RDB$INDEX_TYPE as IndexType '+
+        'FROM '+
+        'RDB$RELATION_CONSTRAINTS R '+
+        'LEFT JOIN RDB$REF_CONSTRAINTS E ON E.RDB$CONSTRAINT_NAME = R.RDB$CONSTRAINT_NAME '+
+        'LEFT JOIN RDB$INDICES I ON I.RDB$INDEX_NAME = R.RDB$INDEX_NAME '+
+        'WHERE '+
+        'R.RDB$RELATION_NAME=''%s'' '+
+        'ORDER BY R.RDB$CONSTRAINT_NAME';
+  }
+  SQLFields = 'SELECT RDB$FIELD_NAME as IndexField '+
+              'FROM RDB$INDEX_SEGMENTS '+
+              'WHERE RDB$INDEX_NAME = :IndexName '+
+              'ORDER BY RDB$FIELD_POSITION';
+        
+Var
+  Q, QF : TSQLQuery;
+  PIndexName : TParam;
+  FConstraintName, FConstraintType,
+  FIndexType, FIndexName, FUnique : TField;
+  //FCheckSource, FRefUnique,
+  //FOnUpdate, FOnDelete : TField;
+  Index : TDDIndexDef;
+
+  procedure BindIndexFields;
+  begin
+    PIndexName := QF.params.parambyname ('IndexName');
+    FConstraintName := Q.Fieldbyname('ConstraintName');
+    FConstraintType := Q.Fieldbyname('ConstraintType');
+    FIndexType := Q.Fieldbyname('IndexType');
+    FIndexName := Q.Fieldbyname('IndexName');
+    FUnique := Q.Fieldbyname('IsUnique');
+  end;
+  {
+  procedure BindCheckFields;
+  begin
+    FCheckSource := Q.Fieldbyname('CheckSource');
+  end;
+  
+  procedure BindForeignFields;
+  begin
+    FRefUnique := Q.Fieldbyname('RefUnique');
+    FOnUpdate := Q.Fieldbyname('OnUpdate');
+    FOnDelete := Q.Fieldbyname('OnDelete');
+  end;
+  }
+  function CreateIndex (AName, indexname: string) : TDDIndexDef;
+  var n, s : string;
+  begin
+    n := trim(AName);
+    if n = '' then
+      n := trim(indexname);
+    if trim (indexName) = '' then
+      indexname := AName;
+    result := Table.Indexes.AddIndex(n);
+    PIndexName.asstring := indexname;
+    QF.Open;
+    try
+      s := trim(QF.Fields[0].asstring);
+      QF.Next;
+      while not QF.eof do
+        begin
+        s := s + ';' + trim(QF.Fields[0].asstring);
+        QF.Next;
+        end;
+    finally
+      QF.Close;
+    end;
+    result.Fields := s;
+  end;
+  
+  function ImportIndices : integer;
+  begin
+    result := 0;
+    Q.SQL.text := format (SQLindexes, [Table.TableName]);
+    Q.Open;
+    try
+      result := 0;
+      Q.First;
+      BindIndexFields;
+      while not Q.eof do
+        begin
+        with CreateIndex (FConstraintName.asstring, FIndexName.asstring) do
+          begin
+          inc (result);
+          if trim(FConstraintType.asstring) = 'PRIMARY KEY' then
+            options := options + [ixPrimary]
+          else if FUnique.asinteger = 1 then
+            options := options + [ixUnique];
+          if FIndextype.asinteger = 1 then
+            options := options + [ixDescending];
+          end;
+        Q.Next;
+        end;
+    finally
+      Q.Close;
+    end;
+  end;
+
+begin
+  Q:=CreateSQLQuery(Nil);
+  try
+    QF:=CreateSQLQuery(Nil);
+    try
+      QF.SQl.Text := SQLFields;
+      QF.Prepare;
+      try
+        ImportIndices;
+        //ImportChecks;
+        //ImportForeignKeys;
+      finally
+        QF.Unprepare;
+      end;
+    finally
+      QF.Free;
+    end;
+  finally
+    Q.Free;
+  end;
+end;
+
+function TSQLDBFBDDEngine.ImportSequences(Sequences: TDDSequenceDefs;
+  List: TStrings; UpdateExisting: boolean): Integer;
+
+const
+  SQL = 'SELECT RDB$GENERATOR_Name FROM RDB$Generators WHERE RDB$System_Flag = 0';
+  
+Var
+  Q : TSQLQuery;
+  Seq : TDDSequenceDef;
+  n : string;
+
+begin
+  result := 0;
+  Q:=CreateSQLQuery(Nil);
+  try
+    Q.Sql.Text := SQL;
+    Q.Open;
+    try
+      while not Q.eof do
+        begin
+        n := trim(Q.Fields[0].asstring);
+        seq := Sequences.FindSequence(n);
+        if not assigned (Seq) then
+          Seq := Sequences.AddSequence(n)
+        else if not UpdateExisting then
+          Seq := nil;
+        if assigned (Seq) then
+          begin
+          Seq.Increment := 0;
+          Seq.StartValue := 0;
+          inc (result);
+          end;
+        Q.Next;
+        end;
+    finally
+      Q.CLose;
+    end;
+  finally
+    Q.Free;
+  end;
+end;
+
+function TSQLDBFBDDEngine.ImportDomains(Domains: TDDDomainDefs; List: TStrings;
+  UpdateExisting: boolean): Integer;
+
+const
+  SQL = 'SELECT ' +
+        ' RDB$FIELD_NAME as Name,' +
+        ' RDB$DEFAULT_SOURCE as DomainDefault,' +
+        ' RDB$FIELD_LENGTH as CharLength,' +
+        ' RDB$FIELD_PRECISION as FieldPrecision,' +
+        ' RDB$FIELD_SCALE as Scale,' +
+        ' RDB$FIELD_TYPE as FieldType,' +
+        ' RDB$FIELD_SUB_TYPE as Subtype,' +
+        ' RDB$NULL_FLAG as DomainNull' +
+        ' FROM '+
+        ' RDB$FIELDS'+
+        ' WHERE RDB$System_Flag = 0 and not (RDB$Field_Name like ''RDB$%'')';
+
+Var
+  Q : TSQLQuery;
+  FName, FDomainName, FDomainDefault,
+  FCharLength, FPrecision, FScale, FFieldType, FSubType, FDomainnull : TField;
+
+  procedure BindFields;
+  begin
+    FName := q.fieldbyname('Name');
+    FDomainDefault := q.fieldbyname('DomainDefault');
+    FCharLength := q.fieldbyname('CharLength');
+    FPrecision := q.fieldbyname('FieldPrecision');
+    FScale := q.fieldbyname('Scale');
+    FFieldType := q.fieldbyname('FieldType');
+    FSubType := q.fieldbyname('SubType');
+    FDomainnull := q.fieldbyname('Domainnull');
+  end;
+
+  function ImportDomain : boolean;
+  var Dom : TDDDomainDef;
+      n : string;
+  begin
+    n := trim(FName.asstring);
+    Dom := Domains.FindDomain(n);
+    if not assigned (Dom) then
+      Dom := Domains.AddDomain(n)
+    else if not UpdateExisting then
+      Dom := nil;
+    if assigned (Dom) then
+      begin
+      result := true;
+      Dom.FieldType := ConvertFBFieldType (FFieldType.asinteger, FSubType.asinteger);
+      Dom.Precision := FPrecision.asinteger;
+      if FScale.asinteger < 0 then
+        Dom.Size := -FScale.asinteger
+      else if Dom.Fieldtype in [ftString, ftFixedChar] then
+        Dom.Size := FCharLength.asinteger
+      else
+        Dom.Size := 0;
+      //Dom.DefaultExpression := copy(trim(FDomainDefault.asstring), 9, maxint);
+      Dom.Required := FDomainnull.asinteger = 1;
+      end
+    else
+      result := false;
+  end;
+  
+begin
+  result := 0;
+  Q:=CreateSQLQuery(Nil);
+  try
+    Q.Sql.Text := SQL;
+    Q.Open;
+    BindFields;
+    try
+      while not Q.eof do
+        begin
+        if ImportDomain then
+          inc (result);
+        Q.Next;
+        end;
+    finally
+      Q.CLose;
+    end;
+  finally
+    Q.Free;
+  end;
+end;
+
 function TSQLDBFBDDEngine.ImportFields(Table: TDDTableDef): Integer;
 Const
   SQL = 'SELECT ' +
@@ -106,7 +427,8 @@ Const
         ' D.RDB$FIELD_SCALE as Scale,' +
         ' D.RDB$FIELD_TYPE as FieldType,' +
         ' D.RDB$FIELD_SUB_TYPE as Subtype,' +
-        ' D.RDB$NULL_FLAG as DomainNull ' +
+        ' D.RDB$NULL_FLAG as DomainNull,' +
+        ' D.RDB$FIELD_NAME as DName ' +
         ' FROM '+
         ' RDB$RELATION_FIELDS F left join RDB$FIELDS D on F.RDB$FIELD_Source = D.RDB$FIELD_NAME'+
         ' WHERE (RDB$RELATION_NAME = ''%s'')' +
@@ -114,8 +436,9 @@ Const
 
 Var
   Q : TSQLQuery;
-  FName, FPosition, FFieldnull, FDescription, FFieldDefault, FDomainDefault,
-  FCharLength, FPrecision, FScale, FFieldType, FSubType, FDomainnull : TField;
+  FName, FPosition, FFieldnull, FDescription, FFieldDefault,
+  FDomainDefault, FDomainnull, FDomainName,
+  FCharLength, FPrecision, FScale, FFieldType, FSubType : TField;
 
   procedure BindFields;
   begin
@@ -131,44 +454,7 @@ Var
     FFieldType := q.fieldbyname('FieldType');
     FSubType := q.fieldbyname('SubType');
     FDomainnull := q.fieldbyname('Domainnull');
-  end;
-
-  function ConvertFBFieldType (FDfieldtype, FBsubtype : integer) : TFieldType;
-  var t : integer;
-      b : byte;
-  begin
-    t := FFieldType.asinteger;
-    if t > 255 then
-      begin
-      if t = 261 then
-        result := ftBlob       {BLOB}
-      else
-        result := ftUnknown;
-      end
-    else
-      begin
-      b := byte(t and $FF);
-      if (b in [7,8,16]) and (FBsubtype <> 0) then
-        // BCD types: 1= Numeric, 2 := Decimal
-        result := ftBCD
-      else
-        case b of
-          14 : result := ftFixedChar; {CHAR}
-          37 : result := ftString;    {VARCHAR}
-          40 : result := ftString;    {CSTRING ?}
-          11 : result := ftFloat;     {D-FLOAT ?}
-          27 : result := ftFloat;     {DOUBLE}
-          10 : result := ftFloat;     {FLOAT}
-          16 : result := ftLargeint;  {INT64}
-          8  : result := ftInteger;   {INTEGER}
-          9  : result := ftlargeint;  {QUAD ?}
-          7  : result := ftSmallint;  {SMALLINT}
-          12 : result := ftDate;      {DATE dialect 3}
-          13 : result := ftTime;      {TIME}
-          35 : result := ftDateTime;  {TIMESTAMP dialect 3, DATE in dialect 1,2}
-          else result := ftUnknown;
-        end;
-      end;
+    FDomainName := q.fieldbyname('DName');
   end;
 
   {Opmerking: bestaande fielddefs die niet meer in de tabel zitten worden niet verwijderd !? }
@@ -201,16 +487,22 @@ Var
     s := trim(FFieldDefault.asstring);
     n := trim(FDomainDefault.asstring);
     if s <> '' then
-      FD.DefaultExpression:=s
-    else if n <> '' then;
-      FD.DefaultExpression:=n;
-    if FFieldnull.asinteger = 1 then
-      FD.Required:=true
-    else if FDomainnull.asinteger = 1 then
-      FD.Required:=true
+      FD.DefaultExpression := copy(s, 9, maxint)
+    else if n <> '' then
+      FD.DefaultExpression := copy(n, 9, maxint);
+    if FDomainnull.asinteger = 0 then
+      if FFieldnull.asinteger = 1 then
+        FD.Required:=true
+      else
+        FD.Required:=false
     else
       FD.Required:=false;
     FD.index := FPosition.AsInteger;
+    s := trim(FDomainName.asstring);
+    if copy(s, 1, 4) <> 'RDB$' then
+      FD.DomainName := s
+    else
+      FD.DomainName := '';
     result := true;
   end;
 

+ 7 - 0
packages/fcl-db/src/datadict/fpddsqldb.pp

@@ -39,6 +39,7 @@ Type
     Function Connect(const AConnectString : String) : Boolean; override;
     Function GetTableList(List : TStrings) : Integer; override;
     Function ImportFields(Table : TDDTableDef) : Integer; override;
+    Function ImportIndexes(Table : TDDTableDef) : Integer; override;
     Function ViewTable(Const TableName: String; DatasetOwner : TComponent) : TDataset; override;
     Function RunQuery(SQL : String) : Integer; override;
     Function CreateQuery(SQL : String; DatasetOwner : TComponent) : TDataset; override;
@@ -141,6 +142,12 @@ begin
   end;
 end;
 
+
+Function TSQLDBDDEngine.ImportIndexes(Table : TDDTableDef) : Integer;
+begin
+end;
+
+
 function TSQLDBDDEngine.ViewTable(const TableName: String;
   DatasetOwner: TComponent): TDataset;
   

+ 1 - 0
packages/fcl-fpcunit/src/fpcunit.pp

@@ -1035,6 +1035,7 @@ begin
   FTests.Add(ATest);
   if ATest.TestSuiteName = '' then
     ATest.TestSuiteName := Self.TestName;
+  ATest.EnableIgnores := Self.EnableIgnores;
 end;
 
 

+ 13 - 0
packages/fcl-fpcunit/src/testdecorator.pp

@@ -33,6 +33,9 @@ type
     function GetTestName: string; override;
     function GetTestSuiteName: string; override;
     procedure SetTestSuiteName(const aName: string); override;
+  protected
+    function GetEnableIgnores: boolean; override;
+    procedure SetEnableIgnores(Value: boolean); override;
   public
     function CountTestCases: integer; override;
     constructor Create(aTest: TTest); reintroduce; overload;
@@ -71,6 +74,16 @@ begin
   FTest.TestSuiteName := aName;
 end;
 
+function TTestDecorator.GetEnableIgnores: boolean;
+begin
+  result := FTest.EnableIgnores;
+end;
+
+procedure TTestDecorator.SetEnableIgnores(Value: boolean);
+begin
+  FTest.EnableIgnores := Value;
+end;
+
 function TTestDecorator.CountTestCases: integer;
 begin
   Result := FTest.CountTestCases;

+ 59 - 59
packages/fcl-image/Makefile

@@ -1,5 +1,5 @@
 #
-# Don't edit, this file is generated by FPCMake Version 2.0.0 [2008/06/15]
+# Don't edit, this file is generated by FPCMake Version 2.0.0 [2008/06/23]
 #
 default: all
 MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian m68k-linux m68k-freebsd m68k-netbsd m68k-amiga m68k-atari m68k-openbsd m68k-palmos m68k-embedded powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macos powerpc-darwin powerpc-morphos powerpc-embedded sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-darwin x86_64-win64 x86_64-embedded arm-linux arm-palmos arm-darwin arm-wince arm-gba arm-nds arm-embedded arm-symbian powerpc64-linux powerpc64-darwin powerpc64-embedded avr-embedded armeb-linux armeb-embedded
@@ -261,178 +261,178 @@ PACKAGESDIR:=$(wildcard $(FPCDIR) $(FPCDIR)/packages $(FPCDIR)/packages/base $(F
 override PACKAGE_NAME=fcl-image
 override PACKAGE_VERSION=2.2.3
 ifeq ($(FULL_TARGET),i386-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),i386-go32v2)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-win32)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),i386-os2)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-freebsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),i386-beos)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-haiku)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-netbsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-solaris)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),i386-qnx)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-netware)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-openbsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-wdosx)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-darwin)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),i386-emx)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-watcom)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-netwlibc)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-wince)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-symbian)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),m68k-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),m68k-freebsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),m68k-netbsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),m68k-amiga)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),m68k-atari)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),m68k-openbsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),m68k-palmos)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),m68k-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),powerpc-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),powerpc-netbsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),powerpc-amiga)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),powerpc-macos)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),powerpc-darwin)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),powerpc-morphos)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),powerpc-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),sparc-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),sparc-netbsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),sparc-solaris)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),sparc-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),x86_64-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),x86_64-freebsd)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),x86_64-darwin)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),x86_64-win64)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),x86_64-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),arm-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),arm-palmos)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),arm-darwin)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),arm-wince)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),arm-gba)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),arm-nds)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),arm-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),arm-symbian)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),powerpc64-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),powerpc64-darwin)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),powerpc64-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),avr-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),armeb-linux)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation  freetypeh freetype ftfont
 endif
 ifeq ($(FULL_TARGET),armeb-embedded)
-override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
+override TARGET_UNITS+=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer extinterpolation
 endif
 ifeq ($(FULL_TARGET),i386-linux)
 override TARGET_RSTS+=pscanvas

+ 1 - 1
packages/fcl-image/Makefile.fpc

@@ -10,7 +10,7 @@ version=2.2.3
 units=fpimgcmn fpimage pngcomn fpreadpng fpwritepng fpreadxpm fpwritexpm \
       clipping fpcanvas pixtools fppixlcanv fpimgcanv pscanvas fpwritebmp \
       fpreadbmp bmpcomn fpreadpnm fpwritepnm fpreadjpeg fpwritejpeg \
-      pcxcomn fpreadpcx fpwritepcx \
+      pcxcomn fpreadpcx fpwritepcx fptiffcmn fpreadtiff fpwritetiff \
       targacmn fpreadtga fpwritetga ellipses fpcolhash fpditherer fpquantizer \ 
       extinterpolation
 units_win32=freetypeh freetype ftfont

+ 17 - 0
packages/fcl-image/fpmake.pp

@@ -30,6 +30,11 @@ begin
         begin
           AddUnit('fpimgcmn');
         end;
+    T:=P.Targets.AddUnit('fptiffcmn.pas');
+      with T.Dependencies do
+        begin
+          AddUnit('fpimage');
+        end;
     T:=P.Targets.AddUnit('clipping.pp');
     T:=P.Targets.AddUnit('ellipses.pp');
       with T.Dependencies do
@@ -134,6 +139,12 @@ begin
           AddUnit('fpimage');
           AddUnit('targacmn');
         end;
+    T:=P.Targets.AddUnit('fpreadtiff.pas');
+      with T.Dependencies do
+        begin
+          AddUnit('fpimage');
+          AddUnit('fptiffcmn');
+        end;
     T:=P.Targets.AddUnit('fpreadxpm.pp');
       with T.Dependencies do
         begin
@@ -175,6 +186,12 @@ begin
           AddUnit('fpimage');
           AddUnit('targacmn');
         end;
+    T:=P.Targets.AddUnit('fpwritetiff.pas');
+      with T.Dependencies do
+        begin
+          AddUnit('fpimage');
+          AddUnit('fptiffcmn');
+        end;
     T:=P.Targets.AddUnit('fpwritexpm.pp');
       with T.Dependencies do
         begin

+ 1272 - 0
packages/fcl-image/src/fpreadtiff.pas

@@ -0,0 +1,1272 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2008 by the Free Pascal development team
+
+    Tiff reader for fpImage.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************
+
+  Working:
+    Grayscale 8,16bit (optional alpha),
+    RGB 8,16bit (optional alpha),
+    Orientation,
+    skipping Thumbnail to read first image,
+    compression: packbits,
+    endian
+
+  ToDo:
+    Compression: deflate, jpeg, ...
+    Planar
+    ColorMap
+    multiple images
+    separate mask
+    pages
+    fillorder - not needed by baseline tiff reader
+    bigtiff 64bit offsets
+}
+unit FPReadTiff;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, FPimage, ctypes, FPTiffCmn;
+
+type
+
+  { TFPReaderTiff }
+
+  TFPReaderTiff = class(TFPCustomImageReader)
+  private
+    FReverserEndian: boolean;
+    IDF: TTiffIDF;
+    FDebug: boolean;
+    fIFDStarts: TFPList;
+    FReverseEndian: Boolean;
+    fStartPos: int64;
+    s: TStream;
+    procedure TiffError(Msg: string);
+    procedure SetStreamPos(p: DWord);
+    function ReadTiffHeader(QuickTest: boolean; out IFD: DWord): boolean; // returns IFD: offset to first IFD
+    function ReadIFD(Start: dword): DWord;// Image File Directory
+    procedure ReadDirectoryEntry(var EntryTag: Word);
+    function ReadEntryUnsigned: DWord;
+    function ReadEntrySigned: Cint32;
+    function ReadEntryRational: TTiffRational;
+    function ReadEntryString: string;
+    function ReadByte: Byte;
+    function ReadWord: Word;
+    function ReadDWord: DWord;
+    procedure ReadValues(StreamPos: DWord;
+                         out EntryType: word; out EntryCount: DWord;
+                         out Buffer: Pointer; out ByteCount: PtrUInt);
+    procedure ReadShortOrLongValues(StreamPos: DWord;
+                                    out Buffer: PDWord; out Count: DWord);
+    procedure ReadShortValues(StreamPos: DWord;
+                              out Buffer: PWord; out Count: DWord);
+    procedure ReadImage(Index: integer);
+    function FixEndian(w: Word): Word; inline;
+    function FixEndian(d: DWord): DWord; inline;
+    procedure DecompressPackBits(var Buffer: Pointer; var Count: PtrInt);
+  protected
+    procedure InternalRead(Str: TStream; AnImage: TFPCustomImage); override;
+    function InternalCheck(Str: TStream): boolean; override;
+  public
+    FirstImg: TTiffIDF;
+    constructor Create; override;
+    destructor Destroy; override;
+    procedure Clear;
+    procedure LoadFromStream(aStream: TStream);
+    property Debug: boolean read FDebug write FDebug;
+    property StartPos: int64 read fStartPos;
+    property ReverserEndian: boolean read FReverserEndian;
+    property TheStream: TStream read s;
+  end;
+
+implementation
+
+procedure TFPReaderTiff.TiffError(Msg: string);
+begin
+  Msg:=Msg+' at position '+IntToStr(s.Position);
+  if fStartPos>0 then
+    Msg:=Msg+'(TiffPosition='+IntToStr(fStartPos)+')';
+  raise Exception.Create(Msg);
+end;
+
+procedure TFPReaderTiff.SetStreamPos(p: DWord);
+var
+  NewPosition: int64;
+begin
+  NewPosition:=Int64(p)+fStartPos;
+  if NewPosition>s.Size then
+    TiffError('Offset outside of stream');
+  s.Position:=NewPosition;
+end;
+
+procedure TFPReaderTiff.LoadFromStream(aStream: TStream);
+var
+  IFDStart: LongWord;
+  i: Integer;
+begin
+  Clear;
+  s:=aStream;
+  fStartPos:=s.Position;
+  ReadTiffHeader(false,IFDStart);
+  i:=0;
+  while IFDStart>0 do begin
+    IFDStart:=ReadIFD(IFDStart);
+    ReadImage(i);
+    inc(i);
+  end;
+end;
+
+function TFPReaderTiff.ReadTiffHeader(QuickTest: boolean; out IFD: DWord): boolean;
+var
+  ByteOrder: String;
+  BigEndian: Boolean;
+  FortyTwo: Word;
+begin
+  Result:=false;
+  // read byte order  II low endian, MM big endian
+  ByteOrder:='  ';
+  s.Read(ByteOrder[1],2);
+  //debugln(['TForm1.ReadTiffHeader ',dbgstr(ByteOrder)]);
+  if ByteOrder='II' then
+    BigEndian:=false
+  else if ByteOrder='MM' then
+    BigEndian:=true
+  else if QuickTest then
+    exit
+  else
+    TiffError('expected II or MM');
+  FReverseEndian:={$IFDEF FPC_BIG_ENDIAN}not{$ENDIF} BigEndian;
+  if Debug then
+    writeln('TFPReaderTiff.ReadTiffHeader Endian Big=',BigEndian,' ReverseEndian=',FReverseEndian);
+  // read magic number 42
+  FortyTwo:=ReadWord;
+  if FortyTwo<>42 then begin
+    if QuickTest then
+      exit
+    else
+      TiffError('expected 42, because of its deep philosophical impact, but found '+IntToStr(FortyTwo));
+  end;
+  // read offset to first IDF
+  IFD:=ReadDWord;
+  //debugln(['TForm1.ReadTiffHeader IFD=',IFD]);
+  Result:=true;
+end;
+
+function TFPReaderTiff.ReadIFD(Start: dword): DWord;
+var
+  Count: Word;
+  i: Integer;
+  EntryTag: Word;
+  p: Int64;
+begin
+  Result:=0;
+  SetStreamPos(Start);
+  Count:=ReadWord;
+  EntryTag:=0;
+  p:=s.Position;
+  for i:=1 to Count do begin
+    ReadDirectoryEntry(EntryTag);
+    inc(p,12);
+    s.Position:=p;
+  end;
+  // read start of next IFD
+  Result:=ReadDWord;
+  if (Result<>0) and (Result<Start) then begin
+    // backward jump: check for loops
+    if fIFDStarts=nil then
+      fIFDStarts:=TFPList.Create
+    else if fIFDStarts.IndexOf(Pointer(PtrUInt(Result)))>0 then
+      TiffError('endless loop in Image File Descriptors');
+    fIFDStarts.Add(Pointer(PtrUInt(Result)));
+  end;
+end;
+
+procedure TFPReaderTiff.ReadDirectoryEntry(var EntryTag: Word);
+var
+  EntryType: Word;
+  EntryCount: LongWord;
+  EntryStart: LongWord;
+  NewEntryTag: Word;
+  UValue: LongWord;
+  WordBuffer: PWord;
+  Count: DWord;
+  i: Integer;
+begin
+  NewEntryTag:=ReadWord;
+  if NewEntryTag<EntryTag then
+    TiffError('Tags must be in ascending order');
+  EntryTag:=NewEntryTag;
+  case EntryTag of
+  254:
+    begin
+      // NewSubFileType
+      UValue:=ReadEntryUnsigned;
+      IDF.ImageIsThumbNail:=UValue and 1<>0;
+      IDF.ImageIsPage:=UValue and 2<>0;
+      IDF.ImageIsMask:=UValue and 4<>0;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry NewSubFileType ThumbNail=',IDF.ImageIsThumbNail,' Page=',IDF.ImageIsPage,' Mask=',IDF.ImageIsMask);
+    end;
+  255:
+    begin
+      // SubFileType (deprecated)
+      UValue:=ReadEntryUnsigned;
+      IDF.ImageIsThumbNail:=false;
+      IDF.ImageIsPage:=false;
+      IDF.ImageIsMask:=false;
+      case UValue of
+      1: ;
+      2: IDF.ImageIsThumbNail:=true;
+      3: IDF.ImageIsPage:=true;
+      else
+        TiffError('SubFileType expected, but found '+IntToStr(UValue));
+      end;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry SubFileType ThumbNail=',IDF.ImageIsThumbNail,' Page=',IDF.ImageIsPage,' Mask=',IDF.ImageIsMask);
+    end;
+  256:
+    begin
+      // fImageWidth
+      IDF.ImageWidth:=ReadEntryUnsigned;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry ImageWidth=',IDF.ImageWidth);
+    end;
+  257:
+    begin
+      // ImageLength
+      IDF.ImageHeight:=ReadEntryUnsigned;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry ImageHeight=',IDF.ImageHeight);
+    end;
+  258:
+    begin
+      // BitsPerSample
+      IDF.BitsPerSample:=DWord(s.Position-fStartPos-2);
+      ReadShortValues(IDF.BitsPerSample,WordBuffer,Count);
+      try
+        SetLength(IDF.BitsPerSampleArray,Count);
+        for i:=0 to Count-1 do
+          IDF.BitsPerSampleArray[i]:=WordBuffer[i];
+      finally
+        ReAllocMem(WordBuffer,0);
+      end;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry BitsPerSample: ');
+        for i:=0 to Count-1 do
+          write(IntToStr(WordBuffer[i]),' ');
+        writeln;
+        ReAllocMem(WordBuffer,0);
+      end;
+    end;
+  259:
+    begin
+      // fCompression
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      1: ; { No fCompression, but pack data into bytes as tightly as possible,
+           leaving no unused bits (except at the end of a row). The component
+           values are stored as an array of type BYTE. Each scan line (row)
+           is padded to the next BYTE boundary. }
+      2: ; { CCITT Group 3 1-Dimensional Modified Huffman run length encoding. }
+      5: ; { LZW }
+      7: ; { JPEG }
+      32946: ; { Deflate }
+      32773: ; { PackBits fCompression, a simple byte-oriented run length scheme.
+               See the PackBits section for details. Data fCompression applies
+               only to raster image data. All other TIFF fields are unaffected. }
+      else
+        TiffError('expected Compression, but found '+IntToStr(UValue));
+      end;
+      IDF.Compression:=UValue;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry Compression=',IntToStr(IDF.Compression),'=');
+        case IDF.Compression of
+        1: write('no compression');
+        2: write('CCITT Group 3 1-Dimensional Modified Huffman run length encoding');
+        5: write('LZW');
+        7: write('JPEG');
+        32946: write('Deflate');
+        32773: write('PackBits');
+        end;
+        writeln;
+      end;
+    end;
+  262:
+    begin
+      // PhotometricInterpretation
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      0: ; // bilevel grayscale 0 is white
+      1: ; // bilevel grayscale 0 is black
+      2: ; // RGB 0,0,0 is black
+      3: ; // Palette color
+      4: ; // Transparency Mask
+      else
+        TiffError('expected PhotometricInterpretation, but found '+IntToStr(UValue));
+      end;
+      IDF.PhotoMetricInterpretation:=UValue;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry PhotometricInterpretation=');
+        case IDF.PhotoMetricInterpretation of
+        0: write('0=bilevel grayscale 0 is white');
+        1: write('1=bilevel grayscale 0 is black');
+        2: write('2=RGB 0,0,0 is black');
+        3: write('3=Palette color');
+        4: write('4=Transparency Mask');
+        end;
+        writeln;
+      end;
+    end;
+  263:
+    begin
+      // Treshholding
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      1: ; // no dithering or halftoning was applied
+      2: ; // an ordered dithering or halftoning was applied
+      3: ; // a randomized dithering or halftoning was applied
+      else
+        TiffError('expected Treshholding, but found '+IntToStr(UValue));
+      end;
+      IDF.Treshholding:=UValue;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry Treshholding=',IDF.Treshholding);
+    end;
+  264:
+    begin
+      // CellWidth
+      IDF.CellWidth:=ReadEntryUnsigned;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry CellWidth=',IDF.CellWidth);
+    end;
+  265:
+    begin
+      // CellLength
+      IDF.CellLength:=ReadEntryUnsigned;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry CellLength=',IDF.CellLength);
+    end;
+  266:
+    begin
+      // FillOrder
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      1: IDF.FillOrder:=1; // left to right = high to low
+      2: IDF.FillOrder:=2; // left to right = low to high
+      else
+        TiffError('expected FillOrder, but found '+IntToStr(UValue));
+      end;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry FillOrder=',IntToStr(IDF.FillOrder),'=');
+        case IDF.FillOrder of
+        1: write('left to right = high to low');
+        2: write('left to right = low to high');
+        end;
+        writeln;
+      end;
+    end;
+  269:
+    begin
+      // DocumentName
+      IDF.DocumentName:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry DocumentName=',IDF.DocumentName);
+    end;
+  270:
+    begin
+      // ImageDescription
+      IDF.ImageDescription:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry ImageDescription=',IDF.ImageDescription);
+    end;
+  271:
+    begin
+      // Make - scanner manufacturer
+      IDF.Make_ScannerManufacturer:=ReadEntryString;
+      writeln('TFPReaderTiff.ReadDirectoryEntry Make_ScannerManufacturer=',IDF.Make_ScannerManufacturer);
+    end;
+  272:
+    begin
+      // Model - scanner model
+      IDF.Model_Scanner:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry Model_Scanner=',IDF.Model_Scanner);
+    end;
+  273:
+    begin
+      // StripOffsets
+      IDF.StripOffsets:=DWord(s.Position-fStartPos-2);
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry StripOffsets=',IDF.StripOffsets);
+    end;
+  274:
+    begin
+      // Orientation
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      1: ;// 0,0 is left, top
+      2: ;// 0,0 is right, top
+      3: ;// 0,0 is right, bottom
+      4: ;// 0,0 is left, bottom
+      5: ;// 0,0 is top, left (rotated)
+      6: ;// 0,0 is top, right (rotated)
+      7: ;// 0,0 is bottom, right (rotated)
+      8: ;// 0,0 is bottom, left (rotated)
+      else
+        TiffError('expected Orientation, but found '+IntToStr(UValue));
+      end;
+      IDF.Orientation:=UValue;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry Orientation=',IntToStr(IDF.Orientation),'=');
+        case IDF.Orientation of
+        1: write('0,0 is left, top');
+        2: write('0,0 is right, top');
+        3: write('0,0 is right, bottom');
+        4: write('0,0 is left, bottom');
+        5: write('0,0 is top, left (rotated)');
+        6: write('0,0 is top, right (rotated)');
+        7: write('0,0 is bottom, right (rotated)');
+        8: write('0,0 is bottom, left (rotated)');
+        end;
+        writeln;
+      end;
+    end;
+  277:
+    begin
+      // SamplesPerPixel
+      IDF.SamplesPerPixel:=ReadEntryUnsigned;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry SamplesPerPixel=',IDF.SamplesPerPixel);
+    end;
+  278:
+    begin
+      // RowsPerStrip
+      UValue:=ReadEntryUnsigned;
+      if UValue=0 then
+        TiffError('expected RowsPerStrip, but found '+IntToStr(UValue));
+      IDF.RowsPerStrip:=UValue;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry RowsPerStrip=',IDF.RowsPerStrip);
+    end;
+  279:
+    begin
+      // StripByteCounts
+      IDF.StripByteCounts:=DWord(s.Position-fStartPos-2);
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry StripByteCounts=',IDF.StripByteCounts);
+    end;
+  280:
+    begin
+      // MinSampleValue
+    end;
+  281:
+    begin
+      // MaxSampleValue
+    end;
+  282:
+    begin
+      // XResolution
+      IDF.XResolution:=ReadEntryRational;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry XResolution=',IDF.XResolution.Numerator,',',IDF.XResolution.Denominator);
+    end;
+  283:
+    begin
+      // YResolution
+      IDF.YResolution:=ReadEntryRational;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry YResolution=',IDF.YResolution.Numerator,',',IDF.YResolution.Denominator);
+    end;
+  284:
+    begin
+      // PlanarConfiguration
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      1: ; // chunky format
+      2: ; // planar format
+      else
+        TiffError('expected PlanarConfiguration, but found '+IntToStr(UValue));
+      end;
+      IDF.PlanarConfiguration:=UValue;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry PlanarConfiguration=');
+        case UValue of
+        1: write('chunky format');
+        2: write('planar format');
+        end;
+        writeln;
+      end;
+    end;
+  288:
+    begin
+      // FreeOffsets
+      // The free bytes in a tiff file are described with FreeByteCount and FreeOffsets
+    end;
+  289:
+    begin
+      // FreeByteCount
+      // The free bytes in a tiff file are described with FreeByteCount and FreeOffsets
+    end;
+  290:
+    begin
+      // GrayResponseUnit
+      // precision of GrayResponseCurve
+    end;
+  291:
+    begin
+      // GrayResponseCurve
+      // the optical density for each possible pixel value
+    end;
+  296:
+    begin
+      // fResolutionUnit
+      UValue:=ReadEntryUnsigned;
+      case UValue of
+      1: IDF.ResolutionUnit:=1; // none
+      2: IDF.ResolutionUnit:=2; // inch
+      3: IDF.ResolutionUnit:=3; // centimeter
+      else
+        TiffError('expected ResolutionUnit, but found '+IntToStr(UValue));
+      end;
+      if Debug then begin
+        write('TFPReaderTiff.ReadDirectoryEntry ResolutionUnit=');
+        case IDF.ResolutionUnit of
+        1: write('none');
+        2: write('inch');
+        3: write('centimeter');
+        end;
+        writeln;
+      end;
+    end;
+  305:
+    begin
+      // Software
+      IDF.Software:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry Software="',IDF.Software,'"');
+    end;
+  306:
+    begin
+      // DateAndTime
+      IDF.DateAndTime:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry DateAndTime="',IDF.DateAndTime,'"');
+    end;
+  315:
+    begin
+      // Artist
+      IDF.Artist:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry Artist="',IDF.Artist,'"');
+    end;
+  316:
+    begin
+      // HostComputer
+      IDF.HostComputer:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry HostComputer="',IDF.HostComputer,'"');
+    end;
+  320:
+    begin
+      // ColorMap: N = 3*2^BitsPerSample
+      IDF.ColorMap:=DWord(s.Position-fStartPos-2);
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry ColorMap');
+    end;
+  338:
+    begin
+      // ExtraSamples: if SamplesPerPixel is bigger than PhotometricInterpretation
+      // then ExtraSamples is an array defining the extra samples
+      // 0=unspecified
+      // 1=alpha (premultiplied)
+      // 2=alpha (unassociated)
+      IDF.ExtraSamples:=DWord(s.Position-fStartPos-2);
+      if Debug then begin
+        ReadShortValues(IDF.ExtraSamples,WordBuffer,Count);
+        write('TFPReaderTiff.ReadDirectoryEntry ExtraSamples: ');
+        for i:=0 to Count-1 do
+          write(IntToStr(WordBuffer[i]),' ');
+        writeln;
+        ReAllocMem(WordBuffer,0);
+      end;
+    end;
+  33432:
+    begin
+      // Copyright
+      IDF.Copyright:=ReadEntryString;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry Copyright="',IDF.Copyright,'"');
+    end;
+  else
+    begin
+      EntryType:=ReadWord;
+      EntryCount:=ReadDWord;
+      EntryStart:=ReadDWord;
+      if Debug then
+        writeln('TFPReaderTiff.ReadDirectoryEntry Tag=',EntryTag,' Type=',EntryType,' Count=',EntryCount,' ValuesStart=',EntryStart);
+    end;
+  end;
+end;
+
+function TFPReaderTiff.ReadEntryUnsigned: DWord;
+var
+  EntryCount: LongWord;
+  EntryType: Word;
+begin
+  Result:=0;
+  EntryType:=ReadWord;
+  EntryCount:=ReadDWord;
+  if EntryCount<>1 then
+    TiffError('EntryCount=1 expected, but found '+IntToStr(EntryCount));
+  //writeln('TFPReaderTiff.ReadEntryUnsigned Tag=',EntryTag,' Type=',EntryType,' Count=',EntryCount,' ValuesStart=',EntryStart]);
+  case EntryType of
+  1: begin
+      // byte: 8bit unsigned
+      Result:=ReadByte;
+    end;
+  3: begin
+      // short: 16bit unsigned
+      Result:=ReadWord;
+    end;
+  4: begin
+      // long: 32bit unsigned long
+      Result:=ReadDWord;
+    end;
+  else
+    TiffError('expected single unsigned value, but found type='+IntToStr(EntryType));
+  end;
+end;
+
+function TFPReaderTiff.ReadEntrySigned: Cint32;
+var
+  EntryCount: LongWord;
+  EntryType: Word;
+begin
+  Result:=0;
+  EntryType:=ReadWord;
+  EntryCount:=ReadDWord;
+  if EntryCount<>1 then
+    TiffError('EntryCount+1 expected, but found '+IntToStr(EntryCount));
+  //writeln('TFPReaderTiff.ReadEntrySigned Tag=',EntryTag,' Type=',EntryType,' Count=',EntryCount,' ValuesStart=',EntryStart]);
+  case EntryType of
+  1: begin
+      // byte: 8bit unsigned
+      Result:=cint8(ReadByte);
+    end;
+  3: begin
+      // short: 16bit unsigned
+      Result:=cint16(ReadWord);
+    end;
+  4: begin
+      // long: 32bit unsigned long
+      Result:=cint32(ReadDWord);
+    end;
+  else
+    TiffError('expected single signed value, but found type='+IntToStr(EntryType));
+  end;
+end;
+
+function TFPReaderTiff.ReadEntryRational: TTiffRational;
+var
+  EntryCount: LongWord;
+  EntryStart: LongWord;
+  EntryType: Word;
+begin
+  Result:=TiffRational0;
+  EntryType:=ReadWord;
+  EntryCount:=ReadDWord;
+  if EntryCount<>1 then
+    TiffError('EntryCount+1 expected, but found '+IntToStr(EntryCount));
+  //writeln('TFPReaderTiff.ReadEntryUnsigned Tag=',EntryTag,' Type=',EntryType,' Count=',EntryCount,' ValuesStart=',EntryStart]);
+  case EntryType of
+  1: begin
+      // byte: 8bit unsigned
+      Result.Numerator:=ReadByte;
+    end;
+  3: begin
+      // short: 16bit unsigned
+      Result.Numerator:=ReadWord;
+    end;
+  4: begin
+      // long: 32bit unsigned long
+      Result.Numerator:=ReadDWord;
+    end;
+  5: begin
+      // rational: Two longs: numerator + denominator
+      // this does not fit into 4 bytes
+      EntryStart:=ReadDWord;
+      SetStreamPos(EntryStart);
+      Result.Numerator:=ReadDWord;
+      Result.Denominator:=ReadDWord;
+    end;
+  else
+    TiffError('expected rational unsigned value, but found type='+IntToStr(EntryType));
+  end;
+end;
+
+function TFPReaderTiff.ReadEntryString: string;
+var
+  EntryType: Word;
+  EntryCount: LongWord;
+  EntryStart: LongWord;
+begin
+  Result:='';
+  EntryType:=ReadWord;
+  if EntryType<>2 then
+    TiffError('asciiz expected, but found '+IntToStr(EntryType));
+  EntryCount:=ReadDWord;
+  EntryStart:=ReadDWord;
+  SetStreamPos(EntryStart);
+  SetLength(Result,EntryCount-1);
+  if EntryCount>1 then
+    s.Read(Result[1],EntryCount-1);
+end;
+
+function TFPReaderTiff.ReadByte: Byte;
+begin
+  Result:=s.ReadByte;
+end;
+
+function TFPReaderTiff.ReadWord: Word;
+begin
+  Result:=FixEndian(s.ReadWord);
+end;
+
+function TFPReaderTiff.ReadDWord: DWord;
+begin
+  Result:=FixEndian(s.ReadDWord);
+end;
+
+procedure TFPReaderTiff.ReadValues(StreamPos: DWord;
+  out EntryType: word; out EntryCount: DWord;
+  out Buffer: Pointer; out ByteCount: PtrUint);
+var
+  EntryStart: DWord;
+begin
+  Buffer:=nil;
+  ByteCount:=0;
+  EntryType:=0;
+  EntryCount:=0;
+  SetStreamPos(StreamPos);
+  ReadWord; // skip tag
+  EntryType:=ReadWord;
+  EntryCount:=ReadDWord;
+  if EntryCount=0 then exit;
+  case EntryType of
+  1,6,7: ByteCount:=EntryCount; // byte
+  2: ByteCount:=EntryCount; // asciiz
+  3,8: ByteCount:=2*EntryCount; // short
+  4,9: ByteCount:=4*EntryCount; // long
+  5,10: ByteCount:=8*EntryCount; // rational
+  11: ByteCount:=4*EntryCount; // single
+  12: ByteCount:=8*EntryCount; // double
+  else
+    TiffError('invalid EntryType '+IntToStr(EntryType));
+  end;
+  if ByteCount>4 then begin
+    EntryStart:=ReadDWord;
+    SetStreamPos(EntryStart);
+  end;
+  GetMem(Buffer,ByteCount);
+  s.Read(Buffer^,ByteCount);
+end;
+
+procedure TFPReaderTiff.ReadShortOrLongValues(StreamPos: DWord; out
+  Buffer: PDWord; out Count: DWord);
+var
+  p: Pointer;
+  ByteCount: PtrUInt;
+  EntryType: word;
+  i: DWord;
+begin
+  Buffer:=nil;
+  Count:=0;
+  p:=nil;
+  try
+    ReadValues(StreamPos,EntryType,Count,p,ByteCount);
+    if Count=0 then exit;
+    if EntryType=3 then begin
+      // short
+      GetMem(Buffer,SizeOf(DWord)*Count);
+      for i:=0 to Count-1 do
+        Buffer[i]:=FixEndian(PWord(p)[i]);
+    end else if EntryType=4 then begin
+      // long
+      Buffer:=p;
+      p:=nil;
+      if FReverseEndian then
+        for i:=0 to Count-1 do
+          Buffer[i]:=FixEndian(PDWord(Buffer)[i]);
+    end else
+      TiffError('only short or long allowed');
+  finally
+    if p<>nil then FreeMem(p);
+  end;
+end;
+
+procedure TFPReaderTiff.ReadShortValues(StreamPos: DWord; out Buffer: PWord;
+  out Count: DWord);
+var
+  p: Pointer;
+  ByteCount: PtrUInt;
+  EntryType: word;
+  i: DWord;
+begin
+  Buffer:=nil;
+  Count:=0;
+  p:=nil;
+  try
+    ReadValues(StreamPos,EntryType,Count,p,ByteCount);
+    if Count=0 then exit;
+    if EntryType=3 then begin
+      // short
+      Buffer:=p;
+      p:=nil;
+      if FReverseEndian then
+        for i:=0 to Count-1 do
+          Buffer[i]:=FixEndian(Buffer[i]);
+    end else
+      TiffError('only short allowed, but found '+IntToStr(EntryType));
+  finally
+    if p<>nil then FreeMem(p);
+  end;
+end;
+
+procedure TFPReaderTiff.ReadImage(Index: integer);
+var
+  StripCount: DWord;
+  StripOffsets: PDWord;
+  StripByteCounts: PDWord;
+  StripIndex: Dword;
+  SOCount: DWord;
+  SBCCount: DWord;
+  CurOffset: DWord;
+  CurByteCnt: PtrInt;
+  Strip: PByte;
+  Run: Dword;
+  y: DWord;
+  y2: DWord;
+  x: DWord;
+  Pixel: DWord;
+  dx: LongInt;
+  dy: LongInt;
+  SampleCnt: DWord;
+  SampleBits: PWord;
+  ExtraSampleCnt: DWord;
+  ExtraSamples: PWord;
+  RedValue: Word;
+  GreenValue: Word;
+  BlueValue: Word;
+  AlphaValue: Word;
+  Col: TFPColor;
+  i: Integer;
+  CurImg: TFPCustomImage;
+  GrayBits: Word;
+  RedBits: Word;
+  GreenBits: Word;
+  BlueBits: Word;
+  AlphaBits: Word;
+  BytesPerPixel: Integer;
+begin
+  if IDF.PhotoMetricInterpretation=High(IDF.PhotoMetricInterpretation) then
+    TiffError('missing PhotometricInterpretation');
+  if IDF.RowsPerStrip=0 then
+    TiffError('missing RowsPerStrip');
+  if IDF.BitsPerSample=0 then
+    TiffError('missing BitsPerSample');
+  if (IDF.ImageWidth=0) or (IDF.ImageHeight=0) then begin
+    exit;
+  end;
+
+  if (Index>0) and (not FirstImg.ImageIsThumbNail) then begin
+    // Image already read
+    exit;
+  end;
+  CurImg:=FirstImg.Img;
+  FirstImg.Assign(IDF);
+
+  ClearTiffExtras(CurImg);
+  // set Tiff extra attributes
+  CurImg.Extra[TiffPhotoMetric]:=IntToStr(IDF.PhotoMetricInterpretation);
+  //writeln('TFPReaderTiff.ReadImage PhotoMetric=',CurImg.Extra[TiffPhotoMetric]);
+  if IDF.Artist<>'' then
+    CurImg.Extra[TiffArtist]:=IDF.Artist;
+  if IDF.Copyright<>'' then
+    CurImg.Extra[TiffCopyright]:=IDF.Copyright;
+  if IDF.DocumentName<>'' then
+    CurImg.Extra[TiffDocumentName]:=IDF.DocumentName;
+  if IDF.DateAndTime<>'' then
+    CurImg.Extra[TiffDateTime]:=IDF.DateAndTime;
+  if IDF.ImageDescription<>'' then
+    CurImg.Extra[TiffImageDescription]:=IDF.ImageDescription;
+  if IDF.Orientation<>0 then
+    CurImg.Extra[TiffOrientation]:=IntToStr(IDF.Orientation);
+  if IDF.ResolutionUnit<>0 then
+    CurImg.Extra[TiffResolutionUnit]:=IntToStr(IDF.ResolutionUnit);
+  if (IDF.XResolution.Numerator<>0) or (IDF.XResolution.Denominator<>0) then
+    CurImg.Extra[TiffXResolution]:=TiffRationalToStr(IDF.XResolution);
+  if (IDF.YResolution.Numerator<>0) or (IDF.YResolution.Denominator<>0) then
+    CurImg.Extra[TiffYResolution]:=TiffRationalToStr(IDF.YResolution);
+  //WriteTiffExtras('ReadImage',CurImg);
+
+  StripCount:=((IDF.ImageHeight-1) div IDF.RowsPerStrip)+1;
+  StripOffsets:=nil;
+  StripByteCounts:=nil;
+  Strip:=nil;
+  ExtraSamples:=nil;
+  SampleBits:=nil;
+  ExtraSampleCnt:=0;
+  try
+    ReadShortOrLongValues(IDF.StripOffsets,StripOffsets,SOCount);
+    if SOCount<>StripCount then
+      TiffError('number of StripCounts is wrong');
+    ReadShortOrLongValues(IDF.StripByteCounts,StripByteCounts,SBCCount);
+    if SBCCount<>StripCount then
+      TiffError('number of StripByteCounts is wrong');
+
+    ReadShortValues(IDF.BitsPerSample,SampleBits,SampleCnt);
+    if SampleCnt<>IDF.SamplesPerPixel then
+      TiffError('Samples='+IntToStr(SampleCnt)+' <> SamplesPerPixel='+IntToStr(IDF.SamplesPerPixel));
+    if IDF.ExtraSamples>0 then
+      ReadShortValues(IDF.ExtraSamples,ExtraSamples,ExtraSampleCnt);
+    if ExtraSampleCnt>=SampleCnt then
+      TiffError('Samples='+IntToStr(SampleCnt)+' ExtraSampleCnt='+IntToStr(ExtraSampleCnt));
+
+    case IDF.PhotoMetricInterpretation of
+    0,1: if SampleCnt-ExtraSampleCnt<>1 then
+      TiffError('gray images expects one sample per pixel, but found '+IntToStr(SampleCnt));
+    2: if SampleCnt-ExtraSampleCnt<>3 then
+      TiffError('rgb images expects three samples per pixel, but found '+IntToStr(SampleCnt));
+    3: if SampleCnt-ExtraSampleCnt<>1 then
+      TiffError('palette images expects one sample per pixel, but found '+IntToStr(SampleCnt));
+    4: if SampleCnt-ExtraSampleCnt<>1 then
+      TiffError('mask images expects one sample per pixel, but found '+IntToStr(SampleCnt));
+    end;
+
+    GrayBits:=0;
+    RedBits:=0;
+    GreenBits:=0;
+    BlueBits:=0;
+    AlphaBits:=0;
+    BytesPerPixel:=0;
+    case IDF.PhotoMetricInterpretation of
+    0,1:
+      begin
+        GrayBits:=SampleBits[0];
+        CurImg.Extra[TiffGrayBits]:=IntToStr(GrayBits);
+        for i:=0 to ExtraSampleCnt-1 do
+          if ExtraSamples[i]=2 then begin
+            AlphaBits:=SampleBits[3+i];
+            CurImg.Extra[TiffAlphaBits]:=IntToStr(AlphaBits);
+          end;
+      end;
+    2:
+      begin
+        RedBits:=SampleBits[0];
+        GreenBits:=SampleBits[0];
+        BlueBits:=SampleBits[0];
+        CurImg.Extra[TiffRedBits]:=IntToStr(RedBits);
+        CurImg.Extra[TiffGreenBits]:=IntToStr(GreenBits);
+        CurImg.Extra[TiffBlueBits]:=IntToStr(BlueBits);
+        for i:=0 to ExtraSampleCnt-1 do
+          if ExtraSamples[i]=2 then begin
+            AlphaBits:=SampleBits[3+i];
+            CurImg.Extra[TiffAlphaBits]:=IntToStr(AlphaBits);
+          end;
+      end;
+    end;
+    BytesPerPixel:=(GrayBits+RedBits+GreenBits+BlueBits+AlphaBits) div 8;
+
+    if not (IDF.FillOrder in [0,1]) then
+      TiffError('FillOrder unsupported: '+IntToStr(IDF.FillOrder));
+
+    for StripIndex:=0 to SampleCnt-1 do begin
+      if not (SampleBits[StripIndex] in [8,16]) then
+        TiffError('SampleBits unsupported: '+IntToStr(SampleBits[StripIndex]));
+    end;
+
+    if CurImg=nil then exit;
+    case IDF.Orientation of
+    0,1..4: CurImg.SetSize(IDF.ImageWidth,IDF.ImageHeight);
+    5..8: CurImg.SetSize(IDF.ImageHeight,IDF.ImageWidth);
+    end;
+
+    y:=0;
+    for StripIndex:=0 to StripCount-1 do begin
+      CurOffset:=StripOffsets[StripIndex];
+      CurByteCnt:=StripByteCounts[StripIndex];
+      //writeln('TFPReaderTiff.ReadImage CurOffset=',CurOffset,' CurByteCnt=',CurByteCnt);
+      if CurByteCnt<=0 then continue;
+      ReAllocMem(Strip,CurByteCnt);
+      SetStreamPos(CurOffset);
+      s.Read(Strip^,CurByteCnt);
+
+      // decompress
+      case IDF.Compression of
+      1: ; // not compressed
+      2: DecompressPackBits(Strip,CurByteCnt); // packbits
+      else
+        TiffError('compression '+IntToStr(IDF.Compression)+' not supported yet');
+      end;
+      if CurByteCnt<=0 then continue;
+
+      Run:=0;
+      dx:=0;
+      dy:=0;
+      for y2:=0 to IDF.RowsPerStrip-1 do begin
+        if y>=IDF.ImageHeight then break;
+        //writeln('TFPReaderTiff.ReadImage y=',y,' IDF.ImageWidth=',IDF.ImageWidth);
+        for x:=0 to IDF.ImageWidth-1 do begin
+          if PtrInt(Run)+BytesPerPixel>CurByteCnt then begin
+            TiffError('TFPReaderTiff.ReadImage Strip too short Run='+IntToStr(Run)+' CurByteCnt='+IntToStr(CurByteCnt)+' x='+IntToStr(x)+' y='+IntToStr(y)+' y2='+IntToStr(y2));
+            break;
+          end;
+          case IDF.PhotoMetricInterpretation of
+          0,1:
+            begin
+              if GrayBits=8 then begin
+                Pixel:=PCUInt8(Strip)[Run];
+                Pixel:=Pixel shl 8+Pixel;
+                inc(Run);
+              end else if GrayBits=16 then begin
+                Pixel:=FixEndian(PCUInt16(@Strip[Run])^);
+                inc(Run,2);
+              end else
+                TiffError('gray image only supported with BitsPerSample 8 or 16 not yet supported');
+              if IDF.PhotoMetricInterpretation=0 then
+                Pixel:=$ffff-Pixel;
+              AlphaValue:=alphaOpaque;
+              for i:=0 to ExtraSampleCnt-1 do begin
+                if ExtraSamples[i]=2 then begin
+                  if SampleBits[3+i]=8 then begin
+                    AlphaValue:=PCUInt8(Strip)[Run];
+                    AlphaValue:=AlphaValue shl 8+AlphaValue;
+                    inc(Run);
+                  end else begin
+                    AlphaValue:=FixEndian(PCUInt16(@Strip[Run])^);
+                    inc(Run,2);
+                  end;
+                end else begin
+                  inc(Run,ExtraSamples[i] div 8);
+                end;
+              end;
+              Col:=FPColor(Pixel,Pixel,Pixel,AlphaValue);
+            end;
+
+          2:
+            begin
+              if RedBits=8 then begin
+                RedValue:=PCUInt8(Strip)[Run];
+                RedValue:=RedValue shl 8+RedValue;
+                inc(Run);
+              end else begin
+                RedValue:=FixEndian(PCUInt16(@Strip[Run])^);
+                inc(Run,2);
+              end;
+              if GreenBits=8 then begin
+                GreenValue:=PCUInt8(Strip)[Run];
+                GreenValue:=GreenValue shl 8+GreenValue;
+                inc(Run);
+              end else begin
+                GreenValue:=FixEndian(PCUInt16(@Strip[Run])^);
+                inc(Run,2);
+              end;
+              if BlueBits=8 then begin
+                BlueValue:=PCUInt8(Strip)[Run];
+                BlueValue:=BlueValue shl 8+BlueValue;
+                inc(Run);
+              end else begin
+                BlueValue:=FixEndian(PCUInt16(@Strip[Run])^);
+                inc(Run,2);
+              end;
+              AlphaValue:=alphaOpaque;
+              for i:=0 to ExtraSampleCnt-1 do begin
+                if ExtraSamples[i]=2 then begin
+                  if SampleBits[3+i]=8 then begin
+                    AlphaValue:=PCUInt8(Strip)[Run];
+                    AlphaValue:=AlphaValue shl 8+AlphaValue;
+                    inc(Run);
+                  end else begin
+                    AlphaValue:=FixEndian(PCUInt16(@Strip[Run])^);
+                    inc(Run,2);
+                  end;
+                end else begin
+                  inc(Run,ExtraSamples[i] div 8);
+                end;
+              end;
+              Col:=FPColor(RedValue,GreenValue,BlueValue,AlphaValue);
+            end;
+          else
+            TiffError('PhotometricInterpretation='+IntToStr(IDF.PhotoMetricInterpretation)+' not supported');
+          end;
+
+          // Orientation
+          case IDF.Orientation of
+          1: begin dx:=x; dy:=y; end;// 0,0 is left, top
+          2: begin dx:=IDF.ImageWidth-x-1; dy:=y; end;// 0,0 is right, top
+          3: begin dx:=IDF.ImageWidth-x-1; dy:=IDF.ImageHeight-y-1; end;// 0,0 is right, bottom
+          4: begin dx:=x; dy:=IDF.ImageHeight-y; end;// 0,0 is left, bottom
+          5: begin dx:=y; dy:=x; end;// 0,0 is top, left (rotated)
+          6: begin dx:=IDF.ImageHeight-y-1; dy:=x; end;// 0,0 is top, right (rotated)
+          7: begin dx:=IDF.ImageHeight-y-1; dy:=IDF.ImageWidth-x-1; end;// 0,0 is bottom, right (rotated)
+          8: begin dx:=y; dy:=IDF.ImageWidth-x-1; end;// 0,0 is bottom, left (rotated)
+          end;
+          CurImg.Colors[dx,dy]:=Col;
+        end;
+        inc(y);
+      end;
+    end;
+  finally
+    ReAllocMem(ExtraSamples,0);
+    ReAllocMem(SampleBits,0);
+    ReAllocMem(StripOffsets,0);
+    ReAllocMem(StripByteCounts,0);
+    ReAllocMem(Strip,0);
+    FirstImg.Assign(IDF);
+  end;
+end;
+
+function TFPReaderTiff.FixEndian(w: Word): Word; inline;
+begin
+  Result:=w;
+  if FReverseEndian then
+    Result:=((Result and $ff) shl 8) or (Result shr 8);
+end;
+
+function TFPReaderTiff.FixEndian(d: DWord): DWord; inline;
+begin
+  Result:=d;
+  if FReverseEndian then
+    Result:=((Result and $ff) shl 24)
+          or ((Result and $ff00) shl 8)
+          or ((Result and $ff0000) shr 8)
+          or (Result shr 24);
+end;
+
+procedure TFPReaderTiff.DecompressPackBits(var Buffer: Pointer; var Count: PtrInt
+  );
+var
+  p: Pcint8;
+  n: cint8;
+  NewBuffer: Pcint8;
+  SrcStep: PtrInt;
+  NewCount: Integer;
+  i: PtrInt;
+  d: pcint8;
+  j: ShortInt;
+begin
+  // compute NewCount
+  NewCount:=0;
+  p:=Pcint8(Buffer);
+  i:=Count;
+  while i>0 do begin
+    n:=p^;
+    case n of
+    0..127:   begin inc(NewCount,n+1);  SrcStep:=n+2; end; // copy the next n+1 bytes
+    -127..-1: begin inc(NewCount,-n+1); SrcStep:=2;   end; // copy the next byte n+1 times
+    else SrcStep:=1; // noop
+    end;
+    inc(p,SrcStep);
+    dec(i,SrcStep);
+  end;
+
+  // decompress
+  if NewCount=0 then begin
+    NewBuffer:=nil;
+  end else begin
+    GetMem(NewBuffer,NewCount);
+    i:=Count;
+    p:=Pcint8(Buffer);
+    d:=Pcint8(NewBuffer);
+    while i>0 do begin
+      n:=p^;
+      case n of
+      0..127:
+        begin
+          // copy the next n+1 bytes
+          inc(NewCount,n+1);  SrcStep:=n+2;
+          System.Move(p[1],d^,n+1);
+          inc(d,n+1);
+        end;
+      -127..-1:
+        begin
+          // copy the next byte n+1 times
+          inc(NewCount,-n+1); SrcStep:=2;
+          j:=-n;
+          n:=p[1];
+          while j>=0 do begin
+            d[j]:=n;
+            dec(j);
+          end;
+        end;
+      else SrcStep:=1; // noop
+      end;
+      inc(p,SrcStep);
+      dec(i,SrcStep);
+    end;
+  end;
+  FreeMem(Buffer);
+  Buffer:=NewBuffer;
+  Count:=NewCount;
+end;
+
+procedure TFPReaderTiff.InternalRead(Str: TStream; AnImage: TFPCustomImage);
+begin
+  FirstImg.Img:=AnImage;
+  try
+    LoadFromStream(Str);
+  finally
+    FirstImg.Img:=nil;
+  end;
+end;
+
+function TFPReaderTiff.InternalCheck(Str: TStream): boolean;
+var
+  IFD: DWord;
+begin
+  try
+    s:=Str;
+    fStartPos:=s.Position;
+    Result:=ReadTiffHeader(true,IFD) and (IFD<>0);
+    s.Position:=fStartPos;
+  except
+    Result:=false;
+  end;
+end;
+
+constructor TFPReaderTiff.Create;
+begin
+  IDF:=TTiffIDF.Create;
+  FirstImg:=TTiffIDF.Create;
+end;
+
+destructor TFPReaderTiff.Destroy;
+begin
+  Clear;
+  FreeAndNil(FirstImg);
+  FreeAndNil(IDF);
+  inherited Destroy;
+end;
+
+procedure TFPReaderTiff.Clear;
+begin
+  IDF.Clear;
+  FirstImg.Clear;
+  FReverseEndian:=false;
+  FreeAndNil(fIFDStarts);
+end;
+
+end.
+

+ 222 - 0
packages/fcl-image/src/fptiffcmn.pas

@@ -0,0 +1,222 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2008 by the Free Pascal development team
+
+    Common stuff for Tiff image format.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************
+}
+unit FPTiffCmn;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, sysutils, FPimage;
+
+type
+  TTiffRational = packed record
+    Numerator, Denominator: DWord;
+  end;
+
+const
+  TiffRational0: TTiffRational = (Numerator: 0; Denominator: 0);
+
+  // TFPCustomImage.Extra properties used by TFPReaderTiff and TFPWriterTiff
+  TiffExtraPrefix = 'Tiff';
+  TiffPhotoMetric = TiffExtraPrefix+'PhotoMetricInterpretation';
+  TiffGrayBits = TiffExtraPrefix+'GrayBits';
+  TiffRedBits = TiffExtraPrefix+'RedBits';
+  TiffGreenBits = TiffExtraPrefix+'GreenBits';
+  TiffBlueBits = TiffExtraPrefix+'BlueBits';
+  TiffAlphaBits = TiffExtraPrefix+'AlphaBits';
+  TiffArtist = TiffExtraPrefix+'Artist';
+  TiffCopyright = TiffExtraPrefix+'Copyright';
+  TiffDocumentName = TiffExtraPrefix+'DocumentName';
+  TiffDateTime = TiffExtraPrefix+'DateTime';
+  TiffImageDescription = TiffExtraPrefix+'ImageDescription';
+  TiffOrientation = TiffExtraPrefix+'Orientation';
+  TiffResolutionUnit = TiffExtraPrefix+'ResolutionUnit';
+  TiffXResolution = TiffExtraPrefix+'XResolution';
+  TiffYResolution = TiffExtraPrefix+'YResolution';
+
+type
+
+  { TTiffIDF }
+
+  TTiffIDF = class
+  public
+    Artist: String;
+    BitsPerSample: DWord; // tiff position of entry
+    BitsPerSampleArray: array of Word;
+    CellLength: DWord;
+    CellWidth: DWord;
+    ColorMap: DWord;// tiff position of entry
+    Compression: DWord;
+    Copyright: string;
+    DateAndTime: string;
+    DocumentName: string;
+    ExtraSamples: DWord;// tiff position of entry
+    FillOrder: DWord;
+    HostComputer: string;
+    ImageDescription: string;
+    ImageHeight: DWord;
+    ImageIsMask: Boolean;
+    ImageIsPage: Boolean;
+    ImageIsThumbNail: Boolean;
+    ImageWidth: DWord;
+    Make_ScannerManufacturer: string;
+    Model_Scanner: string;
+    Orientation: DWord;
+    PhotoMetricInterpretation: DWord;
+    PlanarConfiguration: DWord;
+    ResolutionUnit: DWord;
+    RowsPerStrip: DWord;
+    SamplesPerPixel: DWord;
+    Software: string;
+    StripByteCounts: DWord;// tiff position of entry
+    StripOffsets: DWord; // tiff position of entry
+    Treshholding: DWord;
+    XResolution: TTiffRational;
+    YResolution: TTiffRational;
+    Img: TFPCustomImage;
+    procedure Clear;
+    procedure Assign(IDF: TTiffIDF);
+  end;
+
+function TiffRationalToStr(const r: TTiffRational): string;
+function StrToTiffRationalDef(const s: string; const Def: TTiffRational): TTiffRational;
+procedure ClearTiffExtras(Img: TFPCustomImage);
+procedure CopyTiffExtras(SrcImg, DestImg: TFPCustomImage);
+procedure WriteTiffExtras(Msg: string; Img: TFPCustomImage);
+
+implementation
+
+function TiffRationalToStr(const r: TTiffRational): string;
+begin
+  Result:=IntToStr(r.Numerator)+'/'+IntToStr(r.Denominator);
+end;
+
+function StrToTiffRationalDef(const s: string; const Def: TTiffRational
+  ): TTiffRational;
+var
+  p: LongInt;
+begin
+  Result:=Def;
+  p:=System.Pos('/',s);
+  if p<1 then exit;
+  Result.Numerator:=StrToIntDef(copy(s,1,p-1),TiffRational0.Numerator);
+  Result.Denominator:=StrToIntDef(copy(s,p+1,length(s)),TiffRational0.Denominator);
+end;
+
+procedure ClearTiffExtras(Img: TFPCustomImage);
+var
+  i: Integer;
+begin
+  for i:=Img.ExtraCount-1 downto 0 do
+    if SysUtils.CompareText(copy(Img.ExtraKey[i],1,4),'Tiff')=0 then
+      Img.RemoveExtra(Img.ExtraKey[i]);
+end;
+
+procedure CopyTiffExtras(SrcImg, DestImg: TFPCustomImage);
+var
+  i: Integer;
+begin
+  ClearTiffExtras(DestImg);
+  for i:=SrcImg.ExtraCount-1 downto 0 do
+    if SysUtils.CompareText(copy(SrcImg.ExtraKey[i],1,4),'Tiff')=0 then
+      DestImg.Extra[SrcImg.ExtraKey[i]]:=SrcImg.ExtraValue[i];
+end;
+
+procedure WriteTiffExtras(Msg: string; Img: TFPCustomImage);
+var
+  i: Integer;
+begin
+  writeln('WriteTiffExtras ',Msg);
+  for i:=Img.ExtraCount-1 downto 0 do
+    //if SysUtils.CompareText(copy(Img.ExtraKey[i],1,4),'Tiff')=0 then
+      writeln('  ',i,' ',Img.ExtraKey[i],'=',Img.ExtraValue[i]);
+end;
+
+{ TTiffIDF }
+
+procedure TTiffIDF.Clear;
+begin
+  PhotoMetricInterpretation:=High(PhotoMetricInterpretation);
+  PlanarConfiguration:=0;
+  Compression:=0;
+  ImageHeight:=0;
+  ImageWidth:=0;
+  ImageIsThumbNail:=false;
+  ImageIsPage:=false;
+  ImageIsMask:=false;
+  BitsPerSample:=0;
+  SetLength(BitsPerSampleArray,0);
+  ResolutionUnit:=0;
+  XResolution:=TiffRational0;
+  YResolution:=TiffRational0;
+  RowsPerStrip:=0;
+  StripOffsets:=0;
+  StripByteCounts:=0;
+  SamplesPerPixel:=0;
+  Artist:='';
+  HostComputer:='';
+  ImageDescription:='';
+  Make_ScannerManufacturer:='';
+  Model_Scanner:='';
+  Copyright:='';
+  DateAndTime:='';
+  Software:='';
+  CellWidth:=0;
+  CellLength:=0;
+  FillOrder:=0;
+  Orientation:=0;
+  Treshholding:=0;
+end;
+
+procedure TTiffIDF.Assign(IDF: TTiffIDF);
+begin
+  PhotoMetricInterpretation:=IDF.PhotoMetricInterpretation;
+  PlanarConfiguration:=IDF.PlanarConfiguration;
+  Compression:=IDF.Compression;
+  ImageHeight:=IDF.ImageHeight;
+  ImageWidth:=IDF.ImageWidth;
+  ImageIsThumbNail:=IDF.ImageIsThumbNail;
+  ImageIsPage:=IDF.ImageIsPage;
+  ImageIsMask:=IDF.ImageIsMask;
+  BitsPerSample:=IDF.BitsPerSample;
+  BitsPerSampleArray:=IDF.BitsPerSampleArray;
+  ResolutionUnit:=IDF.ResolutionUnit;
+  XResolution:=IDF.XResolution;
+  YResolution:=IDF.YResolution;
+  RowsPerStrip:=IDF.RowsPerStrip;
+  StripOffsets:=IDF.StripOffsets;
+  StripByteCounts:=IDF.StripByteCounts;
+  SamplesPerPixel:=IDF.SamplesPerPixel;
+  Artist:=IDF.Artist;
+  HostComputer:=IDF.HostComputer;
+  ImageDescription:=IDF.ImageDescription;
+  Make_ScannerManufacturer:=IDF.Make_ScannerManufacturer;
+  Model_Scanner:=IDF.Model_Scanner;
+  Copyright:=IDF.Copyright;
+  DateAndTime:=IDF.DateAndTime;
+  Software:=IDF.Software;
+  CellWidth:=IDF.CellWidth;
+  CellLength:=IDF.CellLength;
+  FillOrder:=IDF.FillOrder;
+  Orientation:=IDF.Orientation;
+  Treshholding:=IDF.Treshholding;
+  if (Img<>nil) and (IDF.Img<>nil) then
+    Img.Assign(IDF.Img);
+end;
+
+end.
+

+ 681 - 0
packages/fcl-image/src/fpwritetiff.pas

@@ -0,0 +1,681 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2008 by the Free Pascal development team
+
+    Tiff reader for fpImage.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************
+
+  Working:
+    Grayscale 8,16bit (optional alpha),
+    RGB 8,16bit (optional alpha),
+    Orientation,
+
+  ToDo:
+    Compression: packbits, deflate, jpeg, ...
+    thumbnail
+    Planar
+    ColorMap
+    multiple images
+    separate mask
+    pages
+    fillorder - not needed by baseline tiff reader
+    bigtiff 64bit offsets
+    endian - currently using system endianess
+}
+unit FPWriteTiff;
+
+{$mode objfpc}{$H+}
+
+{off $DEFINE VerboseTiffWriter}
+
+interface
+
+uses
+  Math, Classes, SysUtils, FPimage, FPTiffCmn, FPWriteTGA;
+
+type
+
+  { TTiffWriteEntry }
+
+  TTiffWriteEntry = class
+  public
+    Tag: Word;
+    EntryType: Word;
+    Count: DWord;
+    Data: Pointer;
+    DataPos: DWord;
+    Bytes: DWord;
+    destructor Destroy; override;
+  end;
+
+  TTiffWriteStrip = record
+    Data: Pointer;
+    Bytes: DWord;
+  end;
+  PTiffWriteStrip = ^TTiffWriteStrip;
+
+  { TTiffWriteStripOffsets }
+
+  TTiffWriteStripOffsets = class(TTiffWriteEntry)
+  public
+    Strips: PTiffWriteStrip;
+    StripByteCounts: TTiffWriteEntry;
+    constructor Create;
+    destructor Destroy; override;
+    procedure SetCount(NewCount: DWord);
+  end;
+
+  { TFPWriterTiff }
+
+  TFPWriterTiff = class(TFPCustomImageWriter)
+  private
+    fStartPos: Int64;
+    FEntries: TFPList; // list of TFPList of TTiffWriteEntry
+    fStream: TStream;
+    fPosition: DWord;
+    procedure ClearEntries;
+    procedure WriteTiff;
+    procedure WriteHeader;
+    procedure WriteIDFs;
+    procedure WriteEntry(Entry: TTiffWriteEntry);
+    procedure WriteData;
+    procedure WriteEntryData(Entry: TTiffWriteEntry);
+    procedure WriteBuf(var Buf; Count: DWord);
+    procedure WriteWord(w: Word);
+    procedure WriteDWord(d: DWord);
+  protected
+    procedure InternalWrite(Stream: TStream; Img: TFPCustomImage); override;
+    procedure AddEntryString(Tag: word; const s: string);
+    procedure AddEntryShort(Tag: word; Value: Word);
+    procedure AddEntryLong(Tag: word; Value: DWord);
+    procedure AddEntryRational(Tag: word; const Value: TTiffRational);
+    procedure AddEntry(Tag: Word; EntryType: Word; EntryCount: DWord;
+                       Data: Pointer; Bytes: DWord;
+                       CopyData: boolean = true);
+    procedure AddEntry(Entry: TTiffWriteEntry);
+    procedure TiffError(Msg: string);
+  public
+    constructor Create; override;
+    destructor Destroy; override;
+    procedure Clear;
+    procedure AddImage(Img: TFPCustomImage);
+    procedure SaveToStream(Stream: TStream);
+  end;
+
+function CompareTiffWriteEntries(Entry1, Entry2: Pointer): integer;
+
+implementation
+
+function CompareTiffWriteEntries(Entry1, Entry2: Pointer): integer;
+begin
+  Result:=integer(TTiffWriteEntry(Entry1).Tag)-integer(TTiffWriteEntry(Entry2).Tag);
+end;
+
+{ TFPWriterTiff }
+
+procedure TFPWriterTiff.WriteWord(w: Word);
+begin
+  if fStream<>nil then
+    fStream.WriteWord(w);
+  inc(fPosition,2);
+end;
+
+procedure TFPWriterTiff.WriteDWord(d: DWord);
+begin
+  if fStream<>nil then
+    fStream.WriteDWord(d);
+  inc(fPosition,4);
+end;
+
+procedure TFPWriterTiff.ClearEntries;
+var
+  i: Integer;
+  List: TFPList;
+  j: Integer;
+begin
+  for i:=FEntries.Count-1 downto 0 do begin
+    List:=TFPList(FEntries[i]);
+    for j:=List.Count-1 downto 0 do
+      TObject(List[j]).Free;
+    List.Free;
+  end;
+  FEntries.Clear;
+end;
+
+procedure TFPWriterTiff.WriteTiff;
+begin
+  {$IFDEF VerboseTiffWriter}
+  writeln('TFPWriterTiff.WriteTiff fStream=',fStream<>nil);
+  {$ENDIF}
+  fPosition:=0;
+  WriteHeader;
+  WriteIDFs;
+  WriteData;
+end;
+
+procedure TFPWriterTiff.WriteHeader;
+var
+  EndianMark: String;
+begin
+  EndianMark:={$IFDEF FPC_BIG_ENDIAN}'MM'{$ELSE}'II'{$ENDIF};
+  WriteBuf(EndianMark[1],2);
+  WriteWord(42);
+  WriteDWord(8);
+end;
+
+procedure TFPWriterTiff.WriteIDFs;
+var
+  i: Integer;
+  List: TFPList;
+  j: Integer;
+  Entry: TTiffWriteEntry;
+  NextIDFPos: DWord;
+begin
+  for i:=0 to FEntries.Count-1 do begin
+    List:=TFPList(FEntries[i]);
+    // write count
+    {$IFDEF VerboseTiffWriter}
+    writeln('TFPWriterTiff.WriteIDFs Count=',List.Count);
+    {$ENDIF}
+    WriteWord(List.Count);
+    // write array of entries
+    for j:=0 to List.Count-1 do begin
+      Entry:=TTiffWriteEntry(List[j]);
+      WriteEntry(Entry);
+    end;
+    // write position of next IDF
+    if i<FEntries.Count-1 then
+      NextIDFPos:=fPosition+4
+    else
+      NextIDFPos:=0;
+    WriteDWord(NextIDFPos);
+  end;
+end;
+
+procedure TFPWriterTiff.WriteEntry(Entry: TTiffWriteEntry);
+var
+  PadBytes: DWord;
+begin
+  {$IFDEF VerboseTiffWriter}
+  writeln('TFPWriterTiff.WriteEntry Tag=',Entry.Tag,' Type=',Entry.EntryType,' Count=',Entry.Count,' Bytes=',Entry.Bytes);
+  {$ENDIF}
+  WriteWord(Entry.Tag);
+  WriteWord(Entry.EntryType);
+  WriteDWord(Entry.Count);
+  if Entry.Bytes<=4 then begin
+    if Entry.Bytes>0 then
+      WriteBuf(Entry.Data^,Entry.Bytes);
+    PadBytes:=0;
+    WriteBuf(PadBytes,4-Entry.Bytes);
+  end else begin
+    WriteDWord(Entry.DataPos);
+  end;
+end;
+
+procedure TFPWriterTiff.WriteData;
+var
+  i: Integer;
+  List: TFPList;
+  j: Integer;
+  Entry: TTiffWriteEntry;
+  Strips: TTiffWriteStripOffsets;
+  k: Integer;
+  Bytes: DWord;
+begin
+  for i:=0 to FEntries.Count-1 do begin
+    List:=TFPList(FEntries[i]);
+    // write entry data
+    for j:=0 to List.Count-1 do begin
+      Entry:=TTiffWriteEntry(List[j]);
+      WriteEntryData(Entry);
+    end;
+    // write strips
+    for j:=0 to List.Count-1 do begin
+      Entry:=TTiffWriteEntry(List[j]);
+      if Entry is TTiffWriteStripOffsets then begin
+        Strips:=TTiffWriteStripOffsets(Entry);
+        // write Strips
+        for k:=0 to Strips.Count-1 do begin
+          PDWord(Strips.Data)[k]:=fPosition;
+          Bytes:=Strips.Strips[k].Bytes;
+          PDWord(Strips.StripByteCounts.Data)[k]:=Bytes;
+          {$IFDEF VerboseTiffWriter}
+          //writeln('TFPWriterTiff.WriteData Strip fPosition=',fPosition,' Bytes=',Bytes);
+          {$ENDIF}
+          if Bytes>0 then
+            WriteBuf(Strips.Strips[k].Data^,Bytes);
+        end;
+      end;
+    end;
+  end;
+end;
+
+procedure TFPWriterTiff.WriteEntryData(Entry: TTiffWriteEntry);
+begin
+  if Entry.Bytes>4 then begin
+    Entry.DataPos:=fPosition;
+    WriteBuf(Entry.Data^,Entry.Bytes);
+  end;
+end;
+
+procedure TFPWriterTiff.WriteBuf(var Buf; Count: DWord);
+begin
+  if Count=0 then exit;
+  if (fStream<>nil) then
+    fStream.Write(Buf,Count);
+  inc(fPosition,Count);
+end;
+
+procedure TFPWriterTiff.AddImage(Img: TFPCustomImage);
+var
+  IDF: TTiffIDF;
+  GrayBits: Word;
+  RedBits: Word;
+  GreenBits: Word;
+  BlueBits: Word;
+  AlphaBits: Word;
+  ImgWidth: DWord;
+  ImgHeight: DWord;
+  Compression: Word;
+  BitsPerSample: array[0..3] of Word;
+  SamplesPerPixel: Integer;
+  BitsPerPixel: DWord;
+  i: Integer;
+  OrientedWidth: DWord;
+  OrientedHeight: DWord;
+  y: integer;
+  x: Integer;
+  StripOffsets: TTiffWriteStripOffsets;
+  Row: DWord;
+  BytesPerLine: DWord;
+  StripBytes: DWord;
+  Strip: PByte;
+  Run: PByte;
+  StripIndex: DWord;
+  Col: TFPColor;
+  Value: Integer;
+  dx: Integer;
+  dy: Integer;
+  CurEntries: TFPList;
+  StripCounts: TTiffWriteEntry;
+begin
+  StripOffsets:=nil;
+  Strip:=nil;
+  IDF:=TTiffIDF.Create;
+  try
+    // add new list of entries
+    CurEntries:=TFPList.Create;
+    FEntries.Add(CurEntries);
+
+    IDF.PhotoMetricInterpretation:=StrToInt64Def(Img.Extra[TiffPhotoMetric],High(IDF.PhotoMetricInterpretation));
+    if not (IDF.PhotoMetricInterpretation in [0,1,2]) then
+      TiffError('PhotoMetricInterpretation='+IntToStr(IDF.PhotometricInterpretation)+' not supported');
+    IDF.Artist:=Img.Extra[TiffArtist];
+    IDF.Copyright:=Img.Extra[TiffCopyright];
+    IDF.DocumentName:=Img.Extra[TiffDocumentName];
+    IDF.DateAndTime:=Img.Extra[TiffDateTime];
+    IDF.ImageDescription:=Img.Extra[TiffImageDescription];
+    IDF.Orientation:=StrToIntDef(Img.Extra[TiffOrientation],1);
+    if not (IDF.Orientation in [1..8]) then
+      IDF.Orientation:=1;
+    IDF.ResolutionUnit:=StrToIntDef(Img.Extra[TiffResolutionUnit],2);
+    if not (IDF.ResolutionUnit in [1..3]) then
+      IDF.ResolutionUnit:=2;
+    IDF.XResolution:=StrToTiffRationalDef(Img.Extra[TiffXResolution],TiffRational0);
+    IDF.YResolution:=StrToTiffRationalDef(Img.Extra[TiffYResolution],TiffRational0);
+
+    GrayBits:=StrToIntDef(Img.Extra[TiffGrayBits],0);
+    RedBits:=StrToIntDef(Img.Extra[TiffRedBits],0);
+    GreenBits:=StrToIntDef(Img.Extra[TiffGreenBits],0);
+    BlueBits:=StrToIntDef(Img.Extra[TiffBlueBits],0);
+    AlphaBits:=StrToIntDef(Img.Extra[TiffAlphaBits],0);
+    ImgWidth:=Img.Width;
+    ImgHeight:=Img.Height;
+    Compression:=1;
+
+    if IDF.Orientation in [1..4] then begin
+      OrientedWidth:=ImgWidth;
+      OrientedHeight:=ImgHeight;
+    end else begin
+      OrientedWidth:=ImgHeight;
+      OrientedHeight:=ImgWidth;
+    end;
+
+    {$IFDEF VerboseTiffWriter}
+    writeln('TFPWriterTiff.AddImage PhotoMetricInterpretation=',IDF.PhotoMetricInterpretation);
+    writeln('TFPWriterTiff.AddImage ImageWidth=',ImgWidth,' ImageHeight=',ImgHeight);
+    writeln('TFPWriterTiff.AddImage Orientation=',IDF.Orientation);
+    writeln('TFPWriterTiff.AddImage ResolutionUnit=',IDF.ResolutionUnit);
+    writeln('TFPWriterTiff.AddImage XResolution=',TiffRationalToStr(IDF.XResolution));
+    writeln('TFPWriterTiff.AddImage YResolution=',TiffRationalToStr(IDF.YResolution));
+    writeln('TFPWriterTiff.AddImage GrayBits=',GrayBits,' RedBits=',RedBits,' GreenBits=',GreenBits,' BlueBits=',BlueBits,' AlphaBits=',AlphaBits);
+    writeln('TFPWriterTiff.AddImage Compression=',Compression);
+    {$ENDIF}
+
+    // required meta entries
+    AddEntryShort(262,IDF.PhotoMetricInterpretation);
+    AddEntryLong(256,ImgWidth);
+    AddEntryLong(257,ImgHeight);
+    AddEntryShort(259,Compression);
+    AddEntryShort(274,IDF.Orientation);
+    AddEntryShort(296,IDF.ResolutionUnit);
+    AddEntryRational(282,IDF.XResolution);
+    AddEntryRational(283,IDF.YResolution);
+    case IDF.PhotoMetricInterpretation of
+    0,1:
+      begin
+        BitsPerSample[0]:=GrayBits;
+        SamplesPerPixel:=1;
+      end;
+    2:
+      begin
+        BitsPerSample[0]:=RedBits;
+        BitsPerSample[1]:=GreenBits;
+        BitsPerSample[2]:=BlueBits;
+        SamplesPerPixel:=3;
+      end;
+    end;
+    if AlphaBits>0 then begin
+      BitsPerSample[SamplesPerPixel]:=AlphaBits;
+      inc(SamplesPerPixel);
+      // ExtraSamples
+      AddEntryShort(338,2);// 2=unassociated alpha
+    end;
+    // BitsPerSample (required)
+    AddEntry(258,3,SamplesPerPixel,@BitsPerSample[0],SamplesPerPixel*2);
+    AddEntryShort(277,SamplesPerPixel);
+
+    // RowsPerStrip (required)
+    BitsPerPixel:=0;
+    for i:=0 to SamplesPerPixel-1 do
+      inc(BitsPerPixel,BitsPerSample[i]);
+    BytesPerLine:=(BitsPerPixel*OrientedWidth+7) div 8;
+    if OrientedWidth=0 then
+      IDF.RowsPerStrip:=8
+    else
+      IDF.RowsPerStrip:=8192 div BytesPerLine;
+    if IDF.RowsPerStrip<1 then
+      IDF.RowsPerStrip:=1;
+    {$IFDEF VerboseTiffWriter}
+    writeln('TFPWriterTiff.AddImage BitsPerPixel=',BitsPerPixel,' OrientedWidth=',OrientedWidth,' BytesPerLine=',BytesPerLine,' RowsPerStrip=',IDF.RowsPerStrip);
+    {$ENDIF}
+    AddEntryLong(278,IDF.RowsPerStrip);
+
+    // optional entries
+    if IDF.Artist<>'' then
+      AddEntryString(315,IDF.Artist);
+    if IDF.Copyright<>'' then
+      AddEntryString(33432,IDF.Copyright);
+    if IDF.DocumentName<>'' then
+      AddEntryString(269,IDF.DocumentName);
+    if IDF.DateAndTime<>'' then
+      AddEntryString(306,IDF.DateAndTime);
+    if IDF.ImageDescription<>'' then
+      AddEntryString(270,IDF.ImageDescription);
+
+    // StripOffsets: StripOffsets, StripByteCounts
+    StripOffsets:=TTiffWriteStripOffsets.Create;
+    AddEntry(StripOffsets);
+    StripCounts:=TTiffWriteEntry.Create;
+    StripCounts.Tag:=279;
+    StripCounts.EntryType:=4;
+    StripOffsets.StripByteCounts:=StripCounts;
+    AddEntry(StripCounts);
+    if OrientedHeight>0 then begin
+      StripOffsets.SetCount((OrientedHeight+IDF.RowsPerStrip-1) div IDF.RowsPerStrip);
+      // compute StripOffsets
+      Row:=0;
+      StripIndex:=0;
+      dx:=0;
+      dy:=0;
+      for y:=0 to OrientedHeight-1 do begin
+        if Row=0 then begin
+          // allocate Strip for the next rows
+          StripBytes:=Min(IDF.RowsPerStrip,OrientedHeight-y)*BytesPerLine;
+          //writeln('TFPWriterTiff.AddImage StripIndex=',StripIndex,' StripBytes=',StripBytes);
+          GetMem(Strip,StripBytes);
+          FillByte(Strip^,StripBytes,0);
+          StripOffsets.Strips[StripIndex].Data:=Strip;
+          StripOffsets.Strips[StripIndex].Bytes:=StripBytes;
+          inc(StripIndex);
+          Run:=Strip;
+        end;
+        // write line
+        for x:=0 to OrientedWidth-1 do begin
+          // Orientation
+          case IDF.Orientation of
+          1: begin dx:=x; dy:=y; end;// 0,0 is left, top
+          2: begin dx:=OrientedWidth-x-1; dy:=y; end;// 0,0 is right, top
+          3: begin dx:=OrientedWidth-x-1; dy:=OrientedHeight-y-1; end;// 0,0 is right, bottom
+          4: begin dx:=x; dy:=OrientedHeight-y; end;// 0,0 is left, bottom
+          5: begin dx:=y; dy:=x; end;// 0,0 is top, left (rotated)
+          6: begin dx:=OrientedHeight-y-1; dy:=x; end;// 0,0 is top, right (rotated)
+          7: begin dx:=OrientedHeight-y-1; dy:=OrientedWidth-x-1; end;// 0,0 is bottom, right (rotated)
+          8: begin dx:=y; dy:=OrientedWidth-x-1; end;// 0,0 is bottom, left (rotated)
+          end;
+          Col:=Img.Colors[dx,dy];
+          case IDF.PhotoMetricInterpretation of
+          0,1:
+            begin
+              // grayscale
+              Value:=(DWord(Col.red)+Col.green+Col.blue) div 3;
+              if IDF.PhotoMetricInterpretation=0 then
+                Value:=$ffff-Value;// 0 is white
+              if GrayBits=8 then begin
+                Run^:=Value shr 8;
+                inc(Run);
+              end else if GrayBits=16 then begin
+                PWord(Run)^:=Value;
+                inc(Run,2);
+              end;
+              if AlphaBits=8 then begin
+                Run^:=Col.alpha shr 8;
+                inc(Run);
+              end else if AlphaBits=16 then begin
+                PWord(Run)^:=Col.alpha;
+                inc(Run,2);
+              end;
+            end;
+          2:
+            begin
+              // RGB
+              if RedBits=8 then begin
+                Run^:=Col.red shr 8;
+                inc(Run);
+              end else if RedBits=16 then begin
+                PWord(Run)^:=Col.red;
+                inc(Run,2);
+              end;
+              if GreenBits=8 then begin
+                Run^:=Col.green shr 8;
+                inc(Run);
+              end else if GreenBits=16 then begin
+                PWord(Run)^:=Col.green;
+                inc(Run,2);
+              end;
+              if BlueBits=8 then begin
+                Run^:=Col.blue shr 8;
+                inc(Run);
+              end else if BlueBits=16 then begin
+                PWord(Run)^:=Col.blue;
+                inc(Run,2);
+              end;
+              if AlphaBits=8 then begin
+                Run^:=Col.alpha shr 8;
+                inc(Run);
+              end else if AlphaBits=16 then begin
+                PWord(Run)^:=Col.alpha;
+                inc(Run,2);
+              end;
+            end;
+          end;
+        end;
+        // next row
+        inc(Row);
+        if (Row=IDF.RowsPerStrip) then
+          Row:=0;
+      end;
+    end;
+
+    CurEntries.Sort(@CompareTiffWriteEntries);
+  finally
+    IDF.Free;
+  end;
+end;
+
+procedure TFPWriterTiff.SaveToStream(Stream: TStream);
+begin
+  fStartPos:=Stream.Position;
+  // simulate write to compute offsets
+  fStream:=nil;
+  WriteTiff;
+  // write to stream
+  fStream:=Stream;
+  WriteTiff;
+  fStream:=nil;
+end;
+
+procedure TFPWriterTiff.InternalWrite(Stream: TStream; Img: TFPCustomImage);
+begin
+  AddImage(Img);
+  SaveToStream(Stream);
+end;
+
+procedure TFPWriterTiff.AddEntryString(Tag: word; const s: string);
+begin
+  if s<>'' then
+    AddEntry(Tag,2,length(s)+1,@s[1],length(s)+1)
+  else
+    AddEntry(Tag,2,0,nil,0);
+end;
+
+procedure TFPWriterTiff.AddEntryShort(Tag: word; Value: Word);
+begin
+  AddEntry(Tag,3,1,@Value,2);
+end;
+
+procedure TFPWriterTiff.AddEntryLong(Tag: word; Value: DWord);
+begin
+  AddEntry(Tag,4,1,@Value,4);
+end;
+
+procedure TFPWriterTiff.AddEntryRational(Tag: word; const Value: TTiffRational
+  );
+begin
+  AddEntry(Tag,5,1,@Value,8);
+end;
+
+procedure TFPWriterTiff.AddEntry(Tag: Word; EntryType: Word; EntryCount: DWord;
+  Data: Pointer; Bytes: DWord; CopyData: boolean);
+var
+  Entry: TTiffWriteEntry;
+begin
+  Entry:=TTiffWriteEntry.Create;
+  Entry.Tag:=Tag;
+  Entry.EntryType:=EntryType;
+  Entry.Count:=EntryCount;
+  if CopyData then begin
+    if Bytes>0 then begin
+      GetMem(Entry.Data,Bytes);
+      System.Move(Data^,Entry.Data^,Bytes);
+    end else begin
+      Entry.Data:=nil;
+    end;
+  end else
+    Entry.Data:=Data;
+  Entry.Bytes:=Bytes;
+  AddEntry(Entry);
+end;
+
+procedure TFPWriterTiff.AddEntry(Entry: TTiffWriteEntry);
+var
+  List: TFPList;
+begin
+  List:=TFPList(FEntries[FEntries.Count-1]);
+  List.Add(Entry);
+end;
+
+procedure TFPWriterTiff.TiffError(Msg: string);
+begin
+  raise Exception.Create('TFPWriterTiff.TiffError: '+Msg);
+end;
+
+constructor TFPWriterTiff.Create;
+begin
+  inherited Create;
+  FEntries:=TFPList.Create;
+end;
+
+destructor TFPWriterTiff.Destroy;
+begin
+  Clear;
+  FreeAndNil(FEntries);
+  inherited Destroy;
+end;
+
+procedure TFPWriterTiff.Clear;
+begin
+  ClearEntries;
+end;
+
+{ TTiffWriteEntry }
+
+destructor TTiffWriteEntry.Destroy;
+begin
+  ReAllocMem(Data,0);
+  inherited Destroy;
+end;
+
+{ TTiffWriteStripOffsets }
+
+constructor TTiffWriteStripOffsets.Create;
+begin
+  Tag:=273;
+  EntryType:=4;
+end;
+
+destructor TTiffWriteStripOffsets.Destroy;
+var
+  i: Integer;
+begin
+  if Strips<>nil then begin
+    for i:=0 to Count-1 do
+      ReAllocMem(Strips[i].Data,0);
+    ReAllocMem(Strips,0);
+  end;
+  inherited Destroy;
+end;
+
+procedure TTiffWriteStripOffsets.SetCount(NewCount: DWord);
+var
+  Size: DWord;
+begin
+  {$IFDEF VerboseTiffWriter}
+  writeln('TTiffWriteStripOffsets.SetCount OldCount=',Count,' NewCount=',NewCount);
+  {$ENDIF}
+  Count:=NewCount;
+  Size:=Count*SizeOf(TTiffWriteStrip);
+  ReAllocMem(Strips,Size);
+  if Size>0 then FillByte(Strips^,Size,0);
+  Size:=Count*SizeOf(DWord);
+  // StripOffsets
+  ReAllocMem(Data,Size);
+  if Size>0 then FillByte(Data^,Size,0);
+  Bytes:=Size;
+  // StripByteCounts
+  ReAllocMem(StripByteCounts.Data,Size);
+  if Size>0 then FillByte(StripByteCounts.Data^,Size,0);
+  StripByteCounts.Count:=Count;
+  StripByteCounts.Bytes:=Size;
+end;
+
+end.
+

+ 3 - 0
packages/fcl-net/src/ssockets.pp

@@ -42,6 +42,7 @@ type
 
   TSocketStream = class(THandleStream)
   Private
+    FSocketInitialized : Boolean;
     FSocketOptions : TSocketOptions;
     Procedure GetSockOptions;
     Procedure SetSocketOptions(Value : TSocketOptions);
@@ -215,11 +216,13 @@ Constructor TSocketStream.Create (AHandle : Longint);
 
 begin
   Inherited Create(AHandle);
+  FSocketInitialized := true;
   GetSockOptions;
 end;
 
 destructor TSocketStream.Destroy;
 begin
+  if FSocketInitialized then
   {$ifdef netware}
   CloseSocket(Handle);
   {$else}

+ 164 - 0
packages/fcl-web/fptemplate.txt

@@ -0,0 +1,164 @@
+fptemplate.pp
+
+implements template support
+
+Default behaviour:
+In the basic default version the TFPTemplate object can handle simple template 
+tags ex. {templatetagname} and requires the replacement strings in a Values 
+array before the parsing starts. An OnGetParam:TGetParamEvent event can be 
+triggered if it is set, when a value is not found in the Values list.
+
+The template tag start and end delimiters can be set with the StartDelimiter 
+and EndDelimiter properties (defaults are '{' and '}' for now).
+
+The parsing happens recursively so a replace text string can contain further 
+tags in it.
+
+
+Recent improvements:
+With the recent improvements the template tag handling got more close to the 
+traditional Delphi way of handling templates.
+By setting the AllowTagParams property to True this new parsing method will be 
+activated and it is possible to pass parameters to the processing program from 
+the template tags.
+
+Other than the two original StartDelimiter and EndDelimiter properties to 
+specify the boundaries of a template tag, there are 3 more delimiters to 
+define these parameters. 
+    ParamStartDelimiter (default is '[-')
+    ParamEndDelimiter   (default is '-]')
+    ParamValueSeparator (default is '=')
+
+Some examples for tags with these above, StartDelimiter:='{+' and 
+EndDelimiter:='+}' 
+(the default '{' and '}' is not good when processing HTML templates with 
+JavaSript in them):
+
+{+ATagHere+}
+
+{+AnotherTagHere  [-paramname1=paramvalue1-]+}
+
+{+HereIsATagToo //with param 
+ [-param1=param1value-]    //some text here to ignore
+//this text is ignored too
+ [-param2=param2value which
+                      is multi line something
+text ending here
+-] 
+ [-param3=param3value-] 
++}
+
+If we want something close to the Delphi tag delimiters, we can set the 
+  StartDelimiter := '<#';
+  EndDelimiter := '>';
+  ParamStartDelimiter := ' ';
+  ParamEndDelimiter := '"';
+  ParamValueSeparator := '="';
+
+This allows the use of Dephi-like tags like these:
+
+<#input type="text" name="foo1"        value="" caption="bar" checked="false">
+<#input type="RadioButton" name="foo2" 
+			     value="" 
+			     caption="bar" checked="false" >
+<#fieldvalue fieldname="FIRSTNAME">
+
+Of course, the above setting requires at least one space before the parameter 
+names. Cannot just use tabs for example to separate them. Also, Delphi (and its
+emulation here) cannot handle any HTML code within the tag parameters because
+some might contain characters indicating tag-param-end or tag-end.
+
+When the tags are processed, for each tag a 
+
+TReplaceTagEvent = Procedure(Sender : TObject; Const TagString : String;
+ TagParams:TStringList; Out ReplaceText : String) Of Object;
+
+will be called with the parameters passed in TagParams:TStringList so it has 
+to be assigned to such a procedure.
+
+Example:
+
+procedure TFPWebModule1.func1callRequest(Sender: TObject; ARequest: TRequest;
+  AResponse: TResponse; var Handled: Boolean);
+var s:String;
+begin     //Template:TFPTemplate is a property of the web Action
+  Template.FileName := 'pathtotemplate\mytemplate.html';
+  Template.AllowTagParams := true;
+  Template.StartDelimiter := '{+';
+  Template.EndDelimiter := '+}';
+  Template.OnReplaceTag := @func1callReplaceTag;
+  s := Template.GetContent;
+
+  //lets use some Delphi style tags too and re-run the parser
+  Template.StartDelimiter := '<#';
+  Template.EndDelimiter := '>';
+  Template.ParamStartDelimiter := ' ';
+  Template.ParamEndDelimiter := '"';
+  Template.ParamValueSeparator := '="';
+  Template.FileName := '';
+  Template.Template := s;
+
+  AResponse.Content := Template.GetContent;
+
+  Handled := true;
+end;
+
+procedure TFPWebModule1.func1callReplaceTag(Sender: TObject; const TagString: 
+  String; TagParams: TStringList; Out ReplaceText: String);
+begin
+  if AnsiCompareText(TagString, 'TagName1') = 0 then
+  begin
+    ReplaceText := 'text to replace this tag, using the TagParams if needed';
+  end else begin
+.
+.snip
+.
+//Not found value for tag -> TagString
+  end;
+end;
+
+
+With these improvements it is easily possible to separate the web page design 
+and the web server side programming. For example to generate a table record 
+list the web designer can use the following Tag in a template:
+
+.
+.snip
+.
+<table class="beautify1"><tr class="beautify2"><td class="beautify3">
+  {+REPORTRESULT 
+   [-HEADER=
+    <table bordercolorlight="#6699CC" bordercolordark="#E1E1E1" class="Label">
+     <tr class="Label" align=center bgcolor="#6699CC">
+      <th><font color="white">~Column1</font></th>
+      <th><font color="white">~Column2</font></th>
+     </tr>
+   -]
+   [- ONEROW =
+     <tr bgcolor="#F2F2F2" class="Label3" align="center">
+      <td>~Column1Value</td><td>~Column2value</td>
+     </tr>
+   -]
+.
+.snip, and so on more parameters
+.
+   [- NOTFOUND=
+    <tr class="Error"><td>There are no entries found.</td></tr> 
+   -]
+   [-FOOTER=</table>-]
+  +}
+</table>
+.
+.snip
+.
+
+
+I know, I know its ugly html progamming and who uses tables and font html tags 
+nowadays, etc. ... but you get the idea.
+The OnReplaceTag event handler just need to replace the whole REPORTRESULT 
+template tag with the ~Column1, ~Column2 for the HEADER parameter, and the 
+~Column1Value, ~Column2Value in the ONEROW parameter while looping through a 
+sql query result set.
+Or if there is nothing to list, just use the NOTFOUND parameter as a replace 
+text for the whole RESULT template tag.
+

+ 3 - 3
packages/fcl-web/src/custcgi.pp

@@ -436,9 +436,9 @@ begin
       end;}
     CT:=ContentType;
     if Pos('MULTIPART/FORM-DATA',Uppercase(CT))<>0 then
-      ProcessMultiPart(M,CT)
+      ProcessMultiPart(M,CT, ContentFields)
     else if CompareText('APPLICATION/X-WWW-FORM-URLENCODED',CT)=0 then
-      ProcessUrlEncoded(M)
+      ProcessUrlEncoded(M, ContentFields)
     else
       begin
 {$ifdef CGIDEBUG}
@@ -465,7 +465,7 @@ begin
 {$endif}
   FQueryString:=GetEnvironmentVariable('QUERY_STRING');
   If (FQueryString<>'') then
-    ProcessQueryString(FQueryString);
+    ProcessQueryString(FQueryString, QueryFields);
 {$ifdef CGIDEBUG}
   SendMethodExit('InitGetVars');
 {$endif}

+ 5 - 2
packages/fcl-web/src/fpapache.pp

@@ -376,11 +376,14 @@ begin
         Raise EFPApacheError.CreateFmt(SErrNoModuleForRequest,[MN]);
         end;
       MC:=MI.ModuleClass;
-      M:=FindModule(MC); // Check if a module exists already
       end;
+    M:=FindModule(MC); // Check if a module exists already
     If (M=Nil) then
       begin
-      M:=MC.Create(Self);
+      If MC.UseStreaming then
+        M:=MC.Create(Self)
+      else  
+        M:=MC.CreateNew(Self,0);
       end;
     M.HandleRequest(ARequest,AResponse);
   except

+ 16 - 3
packages/fcl-web/src/fpcgi.pp

@@ -75,12 +75,20 @@ end;
 
 function TCGIApplication.GetModuleName(Arequest: TRequest): string;
 
+Var
+  S : String;
 
 begin
   If (FModuleVar<>'') then
-    Result:=ARequest.QueryFields.Values[FModuleVar];
+    Result:=ARequest.QueryFields.Values[FModuleVar];//Module name from query parameter using the FModuleVar as parameter name (default is 'Module')
   If (Result='') then
+    begin
+    S:=ARequest.PathInfo;
+    Delete(S,1,1);
+    if (Pos('/',S) <= 0) and AllowDefaultModule then 
+      Exit;//There is only 1 '/' in ARequest.PathInfo -> only ActionName is there -> use default module
     Result:=ARequest.GetNextPathInfo;
+    end;
 end;
 
 function TCGIApplication.FindModule(ModuleClass : TCustomHTTPModuleClass): TCustomHTTPModule;
@@ -137,10 +145,15 @@ begin
       Raise EFPCGIError.CreateFmt(SErrNoModuleForRequest,[MN]);
       end;
     MC:=MI.ModuleClass;
-    M:=FindModule(MC); // Check if a module exists already
     end;
+  M:=FindModule(MC); // Check if a module exists already
   If (M=Nil) then
-    M:=MC.Create(Self);
+    begin
+    If MC.UseStreaming then
+      M:=MC.Create(Self)
+    else  
+      M:=MC.CreateNew(Self,0);
+    end;  
   M.HandleRequest(ARequest,AResponse);
 end;
 

+ 9 - 0
packages/fcl-web/src/fphttp.pp

@@ -94,6 +94,7 @@ Type
   
   TCustomHTTPModule = Class(TDataModule)
   public
+    Class Function UseStreaming : Boolean; virtual;
     Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse); virtual; abstract;
   end;
   
@@ -407,6 +408,14 @@ begin
     Dec(Result);
 end;
 
+{ TCustomHTTPModule }
+
+Class Function TCustomHTTPModule.UseStreaming : Boolean; 
+
+begin
+  Result:=True;
+end;
+    
 Initialization
   ModuleFactory:=TModuleFactory.Create(TModuleItem);
 

+ 255 - 70
packages/fcl-web/src/fptemplate.pp

@@ -28,14 +28,20 @@ Const
   MaxDelimLength    = 5;
   
 Type
-  TParseDelimiter = String[5];
+  TParseDelimiter = String[MaxDelimLength];
   
 Var
-  DefaultStartDelimiter : TParseDelimiter = '{';
-  DefaultEndDelimiter  : TParseDelimiter = '}';
+  DefaultStartDelimiter : TParseDelimiter = '{';           //Template tag start                  |If you want Delphi-like, set it to '<#'
+  DefaultEndDelimiter  : TParseDelimiter = '}';            //Template tag end                    |                                   '>'
+  DefaultParamStartDelimiter  : TParseDelimiter = '[-';    //Tag parameter start                 |                                   ' '
+  DefaultParamEndDelimiter    : TParseDelimiter = '-]';    //Tag parameter end                   |                                   '"'
+  DefaultParamValueSeparator  : TParseDelimiter = '=';     //Tag parameter name/value separator  |                                   '="'
+                                                           //                                    |for tags like <#TagName paramname1="paramvalue1" paramname2="paramvalue2">
 
 Type
-  TGetParamEvent = Procedure(Sender : TObject; Const ParamName : String; Out AValue : String) Of Object;
+  TGetParamEvent = Procedure(Sender : TObject; Const ParamName : String; Out AValue : String) Of Object;                              //for simple template tag support only (ex: {Name})
+  TReplaceTagEvent = Procedure(Sender : TObject; Const TagString : String; TagParams:TStringList; Out ReplaceText : String) Of Object;//for tags with parameters support
+
 
   { TTemplateParser }
 
@@ -45,9 +51,15 @@ Type
     FMaxParseDepth : Integer;
     FEndDelimiter: TParseDelimiter;
     FStartDelimiter: TParseDelimiter;
-    FRecursive: Boolean;
-    FValues : TStringList;
-    FOnGetParam: TGetParamEvent;
+    FParamStartDelimiter: TParseDelimiter;
+    FParamEndDelimiter: TParseDelimiter;
+    FParamValueSeparator: TParseDelimiter;
+    FAllowTagParams: Boolean; //default is false -> simple template tags allowed only [FValues, FOnGetParam (optional) used];
+                              //if true -> template tags with parameters allowed, [FOnReplaceTag] is used for all tag replacements
+    FRecursive: Boolean;                                   //when only simple tags are used in a template (AllowTagParams=false), the replacement can
+    FValues : TStringList;                                 //contain further tags for recursive processing (only used when no tag params are allowed)
+    FOnGetParam: TGetParamEvent;                           //Event handler to use for templates containing simple tags only (ex: {Name})
+    FOnReplaceTag: TReplaceTagEvent;                       //Event handler to use for templates containing tags with parameters (ex: <#TagName paramname1="paramvalue1" paramname2="paramvalue2">)
     function GetDelimiter(Index: integer): TParseDelimiter;
     function GetValue(Key : String): String;
     procedure SetDelimiter(Index: integer; const AValue: TParseDelimiter);
@@ -56,15 +68,22 @@ Type
     Constructor Create;
     Destructor Destroy; override;
     Procedure Clear;
-    Function GetParam(Const Key : String; Out AValue : String) : Boolean;
+    Function ReplaceTag(const Key: String; TagParams:TStringList; out ReplaceWith: String): Boolean;//used only when AllowTagParams = true
+    Function GetParam(Const Key : String; Out AValue : String) : Boolean;                           //used only when AllowTagParams = false
+    Procedure GetTagParams(var TagName:String; var TagParams : TStringList) ;
     Function ParseString(Src : String) : String;
     Function ParseStream(Src : TStream; Dest : TStream) : Integer; // Wrapper, Returns number of bytes written.
-    Procedure ParseStrings(Src : TStrings; Dest : TStrings) ; // Wrapper
-    Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;  // Called if not found in values
-    Property StartDelimiter : TParseDelimiter Index 1 Read GetDelimiter Write SetDelimiter; // Start char/string, default '}'
-    Property EndDelimiter : TParseDelimiter Index 2 Read GetDelimiter Write SetDelimiter;  // end char/string, default '}'
-    Property Values[Key : String] : String Read GetValue Write SetValue; // Contains static values.
-    Property Recursive : Boolean Read FRecursive Write FRecursive;
+    Procedure ParseStrings(Src : TStrings; Dest : TStrings) ;      // Wrapper
+    Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;               // Called if not found in values  //used only when AllowTagParams = false
+    Property OnReplaceTag : TReplaceTagEvent Read FOnReplaceTag Write FOnReplaceTag;       // Called if a tag found          //used only when AllowTagParams = true
+    Property StartDelimiter : TParseDelimiter Index 1 Read GetDelimiter Write SetDelimiter;// Start char/string, default '}'
+    Property EndDelimiter : TParseDelimiter Index 2 Read GetDelimiter Write SetDelimiter;  // end char/string, default '{'
+    Property ParamStartDelimiter : TParseDelimiter Index 3 Read GetDelimiter Write SetDelimiter;
+    Property ParamEndDelimiter : TParseDelimiter Index 4 Read GetDelimiter Write SetDelimiter;
+    Property ParamValueSeparator : TParseDelimiter Index 5 Read GetDelimiter Write SetDelimiter;
+    Property Values[Key : String] : String Read GetValue Write SetValue; // Contains static values.                          //used only when AllowTagParams = false
+    Property Recursive : Boolean Read FRecursive Write FRecursive;                                                           //used only when AllowTagParams = false
+    Property AllowTagParams : Boolean Read FAllowTagParams Write FAllowTagParams;
   end;
 
   { TFPCustomTemplate }
@@ -73,11 +92,17 @@ Type
   private
     FEndDelimiter: TParseDelimiter;
     FStartDelimiter: TParseDelimiter;
+    FParamStartDelimiter: TParseDelimiter;
+    FParamEndDelimiter: TParseDelimiter;
+    FParamValueSeparator: TParseDelimiter;
     FFileName: String;
     FTemplate: String;
-    FOnGetParam: TGetParamEvent;
+    FOnGetParam: TGetParamEvent;                                                                                             //used only when AllowTagParams = false
+    FOnReplaceTag: TReplaceTagEvent;                                                                                         //used only when AllowTagParams = true
+    FAllowTagParams: Boolean;
   Protected
-    Procedure GetParam(Sender : TObject; Const ParamName : String; Out AValue : String);virtual;
+    Procedure GetParam(Sender : TObject; Const ParamName : String; Out AValue : String);virtual;                             //used only when AllowTagParams = false
+    Procedure ReplaceTag(Sender : TObject; Const TagName: String; TagParams:TStringList; Out AValue: String);virtual;        //used only when AllowTagParams = true
     Function CreateParser : TTemplateParser; virtual;
   Public
     Function HasContent : Boolean;
@@ -85,15 +110,22 @@ Type
     Procedure Assign(Source : TPersistent); override;
     Property StartDelimiter : TParseDelimiter Read FStartDelimiter Write FStartDelimiter;
     Property EndDelimiter : TParseDelimiter Read FEndDelimiter Write FEndDelimiter;
+    Property ParamStartDelimiter : TParseDelimiter Read FParamStartDelimiter Write FParamStartDelimiter;
+    Property ParamEndDelimiter : TParseDelimiter Read FParamEndDelimiter Write FParamEndDelimiter;
+    Property ParamValueSeparator : TParseDelimiter Read FParamValueSeparator Write FParamValueSeparator;
     Property FileName : String Read FFileName Write FFileName;
     Property Template : String Read FTemplate Write FTemplate;
     Property OnGetParam : TGetParamEvent Read FOnGetParam Write FOnGetParam;
+    Property OnReplaceTag : TReplaceTagEvent Read FOnReplaceTag Write FOnReplaceTag;
+    Property AllowTagParams : Boolean Read FAllowTagParams Write FAllowTagParams;
   end;
   
   TFPTemplate = Class(TFPCustomTemplate)
   Published
     Property FileName;
     Property Template;
+//    Property AllowTagParams;
+//    Property OnReplaceTag;
   end;
   
   ETemplateParser = Class(Exception);
@@ -128,6 +160,8 @@ begin
   FValue:=AValue;
 end;
 
+{ TTemplateParser }
+
 function TTemplateParser.GetValue(Key : String): String;
 
 Var
@@ -145,10 +179,14 @@ end;
 
 function TTemplateParser.GetDelimiter(Index: integer): TParseDelimiter;
 begin
-  If Index=1 then
-    Result:=FStartDelimiter
-  else
-    Result:=FEndDelimiter;
+  case Index of
+  1: Result:=FStartDelimiter;
+  2: Result:=FEndDelimiter;
+  3: Result:=FParamStartDelimiter;
+  4: Result:=FParamEndDelimiter;
+    else
+     Result:=FParamValueSeparator;
+  end;
 end;
 
 procedure TTemplateParser.SetDelimiter(Index: integer;
@@ -156,10 +194,14 @@ procedure TTemplateParser.SetDelimiter(Index: integer;
 begin
   If Length(AValue)=0 then
     Raise ETemplateParser.Create(SErrNoEmptyDelimiters);
-  If Index=1 then
-    FStartDelimiter:=AValue
-  else
-    FEndDelimiter:=AValue;
+  case Index of
+    1: FStartDelimiter:=AValue;
+    2: FEndDelimiter:=AValue;
+    3: FParamStartDelimiter:=AValue;
+    4: FParamEndDelimiter:=AValue;
+      else
+       FParamValueSeparator:=AValue;
+  end;
 
 end;
 
@@ -167,8 +209,7 @@ procedure TTemplateParser.SetValue(Key : String; const AValue: String);
 
 Var
   I : Integer;
-  SI : TStringItem;
-  
+
 begin
   If (AValue='') then
     begin
@@ -200,9 +241,14 @@ end;
 constructor TTemplateParser.Create;
 
 begin
+  FParseLevel:=0;
   FMaxParseDepth:=MaxParseDepth;
   FStartDelimiter:=DefaultStartDelimiter;
   FEndDelimiter:=DefaultEndDelimiter;
+  FParamStartDelimiter:=DefaultParamStartDelimiter;
+  FParamEndDelimiter:=DefaultParamEndDelimiter;
+  FParamValueSeparator:=DefaultParamValueSeparator;
+  FAllowTagParams := false;
 end;
 
 destructor TTemplateParser.Destroy;
@@ -247,6 +293,13 @@ begin
     AValue:=ParseString(AValue);
 end;
 
+function TTemplateParser.ReplaceTag(const Key: String; TagParams:TStringList; out ReplaceWith: String): Boolean;
+begin
+  Result:=Assigned(FOnReplaceTag);
+  If Result then
+    FOnReplaceTag(Self,Key,TagParams,ReplaceWith);
+end;
+
 Function FindDelimiter(SP : PChar; D : TParseDelimiter; MaxLen : Integer) : PChar; Inline;
 
 Var
@@ -298,62 +351,168 @@ begin
   Move(P^,S[Slen+1],NChars);
 end;
 
+procedure TTemplateParser.GetTagParams(var TagName:String; var TagParams : TStringList) ;
+var
+  I,SLen:Integer;
+  TS,TM,TE,SP,P : PChar;
+  PName, PValue, TP : String;
+  IsFirst:Boolean;
+begin
+  SLen:=Length(TagName);
+  if SLen=0 then exit;
+
+  IsFirst := true;
+  SP:=PChar(TagName);
+  TP := TagName;
+  P:=SP;
+  while (P-SP<SLen) do
+  begin
+    TS:=FindDelimiter(P,FParamStartDelimiter,SLen-(P-SP));
+    if (TS<>Nil) then
+    begin//Found param start delimiter
+      if IsFirst then
+      begin//Get the real Tag name
+        IsFirst := false;
+        I := 1;
+        while not (P[I] in [#0..' ']) do Inc(I);
+        SetLength(TP, I);
+        Move(P^, TP[1], I);
+      end;
+      inc(TS, Length(FParamStartDelimiter));
+      I:=TS-P;//index of param name
+      TM:=FindDelimiter(TS,FParamValueSeparator,SLen-I+1);
+      if (TM<>Nil) then
+      begin//Found param value separator
+        I:=TM-TS;//lenght of param name
+        SetLength(PName, I);
+        Move(TS^, PName[1], I);//param name
+        inc(TS, Length(FParamValueSeparator) + I);
+        I := TS - P;//index of param value
+        TE:=FindDelimiter(TS,FParamEndDelimiter, SLen-I+1);
+        if (TE<>Nil) then
+        begin//Found param end
+          I:=TE-TS;//Param length
+          Setlength(PValue,I);
+          Move(TS^,PValue[1],I);//Param value
+          TagParams.Add(Trim(PName) + '=' + PValue);//Param names cannot contain '='
+          P:=TE+Length(FParamEndDelimiter);
+          TS:=P;
+        end else break;
+      end else break;
+    end else break;
+  end;
+  TagName := Trim(TP);
+end;
+
 function TTemplateParser.ParseString(Src: String): String;
 
 Var
-  PN,PV : String;
-  i,RLen,SLen,STlen : Integer;
+  PN,PV,ReplaceWith : String;
+  i,SLen : Integer;
   TS,TE,SP,P : PChar;
-
+  TagParams:TStringList;
 begin
-  Inc(FParseLevel);
-  If FParseLevel>FMaxParseDepth then
-    Raise ETemplateParser.CreateFmt(SErrParseDepthExceeded,[FMaxParseDepth]);
-  SLen:=Length(Src); // Minimum
-  If SLen=0 then
-    exit;
-  STLen:=Length(FStartDelimiter);
-  Result:='';
-  SP:=PChar(Src);
-  P:=SP;
-  While (P-SP<SLen) do
-    begin
-    TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
-    If (TS=Nil) then
-      begin
-      TS:=P;
-      P:=SP+SLen
-      end
-    else
+  if FAllowTagParams then
+  begin//template tags with parameters are allowed
+    SLen:=Length(Src);
+    If SLen=0 then
+      exit;
+    Result:='';
+    SP:=PChar(Src);
+    P:=SP;
+    While (P-SP<SLen) do
       begin
-      I:=TS-P;
-      TE:=FindDelimiter(TS,FendDelimiter,SLen-I+1);
-      If (TE=Nil) then
-        begin
+      TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
+      If (TS=Nil) then
+        begin//Tag Start Delimiter not found
         TS:=P;
         P:=SP+SLen;
         end
       else
         begin
-        // Add text prior to template to result
-        AddToString(Result,P,I);
-        // retrieve template name
-        inc(TS,Length(FendDelimiter));
-        I:=TE-TS;
-        Setlength(PN,I);
-        Move(TS^,PN[1],I);
-        If GetParam(PN,PV) then
+        I:=TS-P;
+        TE:=FindDelimiter(TS,FEndDelimiter,SLen-I+1);
+        If (TE=Nil) then
+          begin//Tag End Delimiter not found
+          TS:=P;
+          P:=SP+SLen;
+          end
+        else//Found start and end delimiters for the Tag
           begin
-          Result:=Result+PV;
+          // Add text prior to template tag to result
+          AddToString(Result,P,I);
+          // Retrieve the full template tag (only tag name if no params specified)
+          inc(TS,Length(FStartDelimiter));//points to first char of Tag name now
+          I:=TE-TS;//full Tag length
+          Setlength(PN,I);
+          Move(TS^,PN[1],I);//full Tag string (only tag name if no params specified)
+          TagParams := TStringList.Create;
+          try
+            TagParams.Sorted := True;
+            GetTagParams(PN, Tagparams);
+            If ReplaceTag(PN,TagParams,ReplaceWith) then
+              Result:=Result+ReplaceWith;
+          finally
+            TagParams.Free;
+          end;
+          P:=TE+Length(FEndDelimiter);
+          TS:=P;
           end;
-        P:=TE+Length(FEndDelimiter);
+        end
+      end;
+    I:=P-TS;
+    If (I>0) then
+      AddToString(Result,TS,I);
+  end else begin//template tags with parameters are not allowed
+    Inc(FParseLevel);
+    If FParseLevel>FMaxParseDepth then
+      Raise ETemplateParser.CreateFmt(SErrParseDepthExceeded,[FMaxParseDepth]);
+    SLen:=Length(Src); // Minimum
+    If SLen=0 then
+      exit;
+//    STLen:=Length(FStartDelimiter);
+    Result:='';
+    SP:=PChar(Src);
+    P:=SP;
+    While (P-SP<SLen) do
+      begin
+      TS:=FindDelimiter(P,FStartDelimiter,SLen-(P-SP));
+      If (TS=Nil) then
+        begin
         TS:=P;
-        end;
-      end
-    end;
-  I:=P-TS;
-  If (I>0) then
-    AddToString(Result,TS,I);
+        P:=SP+SLen
+        end
+      else
+        begin
+        I:=TS-P;
+        TE:=FindDelimiter(TS,FEndDelimiter,SLen-I+1);
+        If (TE=Nil) then
+          begin
+          TS:=P;
+          P:=SP+SLen;
+          end
+        else
+          begin
+          // Add text prior to template to result
+          AddToString(Result,P,I);
+          // retrieve template name
+          inc(TS,Length(FStartDelimiter));
+          I:=TE-TS;
+          Setlength(PN,I);
+          Move(TS^,PN[1],I);
+          If GetParam(PN,PV) then
+            begin
+            Result:=Result+PV;
+            end;
+          P:=TE+Length(FEndDelimiter);
+          TS:=P;
+          end;
+        end
+      end;
+    I:=P-TS;
+    If (I>0) then
+      AddToString(Result,TS,I);
+  end;
 end;
 
 function TTemplateParser.ParseStream(Src: TStream; Dest: TStream): Integer;
@@ -370,6 +529,7 @@ begin
   Finally
     SS.Free;
   end;
+  FParseLevel := 0;
   R:=ParseString(S);
   Result:=Length(R);
   If (Result>0) then
@@ -383,28 +543,48 @@ Var
 
 begin
   For I:=0 to Src.Count-1 do
+  begin
+    FParseLevel := 0;
     Dest.Add(ParseString(Src[i]));
+  end;
 end;
 
 { TFPCustomTemplate }
 
-procedure TFPCustomTemplate.GetParam(Sender: TObject; const ParamName: String;
-  out AValue: String);
+procedure TFPCustomTemplate.GetParam(Sender: TObject; const ParamName: String; out AValue: String);
   
 begin
   If Assigned(FOnGetParam) then
    FOnGetParam(Self,ParamName,AValue);
 end;
 
+procedure TFPCustomTemplate.ReplaceTag(Sender: TObject; const TagName: String; TagParams:TStringList; Out AValue: String);
+
+begin
+  If Assigned(FOnReplaceTag) then
+  begin
+    FOnReplaceTag(Self,TagName,TagParams,AValue);
+  end;
+end;
+
 function TFPCustomTemplate.CreateParser: TTemplateParser;
 
 begin
   Result:=TTemplateParser.Create;
+  Result.FParseLevel := 0;
   If (FStartDelimiter<>'') then
     Result.StartDelimiter:=FStartDelimiter;
   If (FEndDelimiter<>'') then
     Result.EndDelimiter:=FEndDelimiter;
+  If (FParamStartDelimiter<>'') then
+    Result.ParamStartDelimiter:=FParamStartDelimiter;
+  If (FParamEndDelimiter<>'') then
+    Result.ParamEndDelimiter:=FParamEndDelimiter;
+  If (FParamValueSeparator<>'') then
+    Result.ParamValueSeparator:=FParamValueSeparator;
   Result.OnGetParam:=@GetParam;
+  Result.OnReplaceTag:=@ReplaceTag;
+  Result.AllowTagParams:=FAllowTagParams;
 end;
 
 function TFPCustomTemplate.HasContent: Boolean;
@@ -461,9 +641,14 @@ begin
     T:=Source as TFPCustomTemplate;
     FEndDelimiter:=T.EndDelimiter;
     FStartDelimiter:=T.StartDelimiter;
+    FParamEndDelimiter:=T.ParamEndDelimiter;
+    FParamStartDelimiter:=T.ParamStartDelimiter;
+    FParamValueSeparator:=T.ParamValueSeparator;
     FFileName:=T.FileName;
     FTemplate:=T.Template;
     FOnGetParam:=T.OnGetParam;
+    FOnReplaceTag:=T.OnReplaceTag;
+    FAllowTagParams := T.AllowTagParams;
     end
   else
     inherited Assign(Source);

+ 13 - 10
packages/fcl-web/src/httpdefs.pp

@@ -268,9 +268,9 @@ type
     procedure ParseFirstHeaderLine(const line: String);override;
     function GetFirstHeaderLine: String;
   Protected
-    Procedure ProcessMultiPart(Stream : TStream; Const Boundary : String); virtual;
-    Procedure ProcessQueryString(Const FQueryString : String); virtual;
-    procedure ProcessURLEncoded(Stream : TStream); virtual;
+    Procedure ProcessMultiPart(Stream : TStream; Const Boundary : String;SL:TStrings); virtual;
+    Procedure ProcessQueryString(Const FQueryString : String; SL:TStrings); virtual;
+    procedure ProcessURLEncoded(Stream : TStream;SL:TStrings); virtual;
     Function  GetTempUploadFileName : String; virtual;
     Property ReturnedPathInfo : String Read FReturnedPathInfo Write FReturnedPathInfo;
   public
@@ -656,6 +656,7 @@ constructor THttpHeader.Create;
 begin
   FCookieFields:=TStringList.Create;
   FQueryFields:=TStringList.Create;
+  FContentFields:=TStringList.Create;
   FHttpVersion := '1.1';
 end;
 
@@ -664,6 +665,7 @@ destructor THttpHeader.Destroy;
 begin
   FreeAndNil(FCookieFields);
   FreeAndNil(FQueryFields);
+  FreeAndNil(FContentFields);
   inherited Destroy;
 end;
 
@@ -935,7 +937,7 @@ begin
     Result := Result + ' HTTP/' + HttpVersion;
 end;
 
-Procedure TRequest.ProcessQueryString(Const FQueryString : String);
+Procedure TRequest.ProcessQueryString(Const FQueryString : String; SL:TStrings);
 
 
 var
@@ -1038,19 +1040,20 @@ begin
     if (QueryItem<>'') then
       begin
       QueryItem:=HTTPDecode(QueryItem);
-      FQueryFields.Add(QueryItem);
+      SL.Add(QueryItem);
       end;
     end;
 {$ifdef CGIDEBUG}SendMethodExit('ProcessQueryString');{$endif CGIDEBUG}
 end;
 
 function TRequest.GetTempUploadFileName: String;
+
 begin
-  Result:=GetTempFileName('/tmp/','CGI')
+  Result := GetTempFileName(GetTempDir, 'CGI');
 end;
 
 
-Procedure TRequest.ProcessMultiPart(Stream : TStream; Const Boundary : String);
+Procedure TRequest.ProcessMultiPart(Stream : TStream; Const Boundary : String; SL:TStrings);
 
 Var
   L : TList;
@@ -1129,7 +1132,7 @@ begin
         end;
       FI.Free;
       L[i]:=Nil;
-      QueryFields.Add(Key+'='+Value)
+      SL.Add(Key+'='+Value)
       end;
   Finally
     For I:=0 to L.Count-1 do
@@ -1139,7 +1142,7 @@ begin
 {$ifdef CGIDEBUG}  SendMethodExit('ProcessMultiPart');{$endif CGIDEBUG}
 end;
 
-Procedure TRequest.ProcessURLEncoded(Stream: TStream);
+Procedure TRequest.ProcessURLEncoded(Stream: TStream; SL:TStrings);
 
 var
   S : String;
@@ -1149,7 +1152,7 @@ begin
   SetLength(S,Stream.Size); // Skip added Null.
   Stream.ReadBuffer(S[1],Stream.Size);
 {$ifdef CGIDEBUG}SendDebugFmt('Query string : %s',[s]);{$endif CGIDEBUG}
-  ProcessQueryString(S);
+  ProcessQueryString(S,SL);
 {$ifdef CGIDEBUG} SendMethodEnter('ProcessURLEncoded');{$endif CGIDEBUG}
 end;
 

+ 100 - 84
packages/fcl-xml/src/dom.pp

@@ -38,7 +38,7 @@ unit DOM;
 interface
 
 uses
-  SysUtils, Classes, AVL_Tree;
+  SysUtils, Classes, AVL_Tree, xmlutils;
 
 // -------------------------------------------------------
 //   DOMException
@@ -221,6 +221,8 @@ type
     function GetPrefix: DOMString; virtual;
     procedure SetPrefix(const Value: DOMString); virtual;
     function GetOwnerDocument: TDOMDocument; virtual;
+    procedure SetReadOnly(Value: Boolean);
+    procedure Changing;
   public
     constructor Create(AOwner: TDOMDocument);
     destructor Destroy; override;
@@ -299,7 +301,7 @@ type
   protected
     FNode: TDOMNode;
     FRevision: Integer;
-    FList: TList;
+    FList: TFPList;
     function GetCount: LongWord;
     function GetItem(index: LongWord): TDOMNode;
     procedure BuildList; virtual;
@@ -333,7 +335,7 @@ type
   protected
     FOwner: TDOMNode;
     FNodeType: Integer;
-    FList: TList;
+    FList: TFPList;
     function GetItem(index: LongWord): TDOMNode;
     function GetLength: LongWord;
     function Find(const name: DOMString; out Index: LongWord): Boolean;
@@ -415,7 +417,7 @@ type
 
   TDOMDocument = class(TDOMNode_WithChildren)
   protected
-    FIDList: TList;
+    FIDList: THashTable;
     FRevision: Integer;
     FXML11: Boolean;
     FImplementation: TDOMImplementation;
@@ -427,8 +429,6 @@ type
     function GetOwnerDocument: TDOMDocument; override;
     procedure SetTextContent(const value: DOMString); override;
     function IndexOfNS(const nsURI: DOMString): Integer;
-    function FindID(const aID: DOMString; out Index: LongWord): Boolean;
-    procedure ClearIDList;
     procedure RemoveID(Elem: TDOMElement);
   public
     property DocType: TDOMDocumentType read GetDocType;
@@ -501,8 +501,7 @@ type
   protected
     FName: DOMString;
     FOwnerElement: TDOMElement;
-    // TODO: following 2 - replace with a link to AttDecl ??    
-    FDeclared: Boolean;
+    // TODO: replace with a link to AttDecl ??    
     FDataType: TAttrDataType;
     function  GetNodeValue: DOMString; override;
     function GetNodeType: Integer; override;
@@ -713,16 +712,6 @@ type
 
 implementation
 
-uses
-  xmlutils;
-
-type
-  PIDItem = ^TIDItem;
-  TIDItem = record
-    ID: WideString;
-    Element: TDOMElement;
-  end;
-
 constructor TRefClass.Create;
 begin
   inherited Create;
@@ -858,12 +847,14 @@ end;
 
 function TDOMNode.InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode;
 begin
+  Changing;  // merely to comply with core3/nodeinsertbefore14
   raise EDOMHierarchyRequest.Create('Node.InsertBefore');
   Result:=nil;
 end;
 
 function TDOMNode.ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode;
 begin
+  Changing;  // merely to comply with core3/nodereplacechild21
   raise EDOMHierarchyRequest.Create('Node.ReplaceChild');
   Result:=nil;
 end;
@@ -1000,6 +991,36 @@ begin
   Result := FOwnerDocument;
 end;
 
+procedure TDOMNode.SetReadOnly(Value: Boolean);
+var
+  child: TDOMNode;
+  attrs: TDOMNamedNodeMap;
+  I: Integer;
+begin
+  if Value then
+    Include(FFlags, nfReadOnly)
+  else
+    Exclude(FFlags, nfReadOnly);
+  child := FirstChild;
+  while Assigned(child) do
+  begin
+    child.SetReadOnly(Value);
+    child := child.NextSibling;
+  end;
+  attrs := Attributes;
+  if Assigned(attrs) then
+  begin
+    for I := 0 to attrs.Length-1 do
+      attrs[I].SetReadOnly(Value);
+  end;
+end;
+
+procedure TDOMNode.Changing;
+begin
+  if nfReadOnly in FFlags then
+    raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'Node.CheckReadOnly');
+end;
+
 function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
 var i: integer;
 begin
@@ -1082,6 +1103,7 @@ begin
   Result := NewChild;
   NewChildType := NewChild.NodeType;
 
+  Changing;
   if NewChild.FOwnerDocument <> FOwnerDocument then
   begin
     if (NewChildType <> DOCUMENT_TYPE_NODE) or
@@ -1171,6 +1193,8 @@ end;
 
 function TDOMNode_WithChildren.DetachChild(OldChild: TDOMNode): TDOMNode;
 begin
+  Changing;
+
   if OldChild.ParentNode <> Self then
     raise EDOMNotFound.Create('NodeWC.RemoveChild');
 
@@ -1266,6 +1290,7 @@ end;
 
 procedure TDOMNode_WithChildren.SetTextContent(const AValue: DOMString);
 begin
+  Changing;
   FreeChildren;
   if AValue <> '' then
     AppendChild(FOwnerDocument.CreateTextNode(AValue));
@@ -1295,7 +1320,7 @@ begin
   inherited Create;
   FNode := ANode;
   FRevision := ANode.GetRevision-1;   // force BuildList at first access
-  FList := TList.Create;
+  FList := TFPList.Create;
 end;
 
 destructor TDOMNodeList.Destroy;
@@ -1395,7 +1420,7 @@ begin
   inherited Create;
   FOwner := AOwner;
   FNodeType := ANodeType;
-  FList := TList.Create;
+  FList := TFPList.Create;
 end;
 
 destructor TDOMNamedNodeMap.Destroy;
@@ -1467,7 +1492,9 @@ var
   AttrOwner: TDOMNode;
 begin
   Result := 0;
-  if arg.FOwnerDocument <> FOwner.FOwnerDocument then
+  if nfReadOnly in FOwner.FFlags then
+    Result := NO_MODIFICATION_ALLOWED_ERR
+  else if arg.FOwnerDocument <> FOwner.FOwnerDocument then
     Result := WRONG_DOCUMENT_ERR
   else if arg.NodeType <> FNodeType then
     Result := HIERARCHY_REQUEST_ERR
@@ -1537,6 +1564,8 @@ end;
 
 function TDOMNamedNodeMap.RemoveNamedItem(const name: DOMString): TDOMNode;
 begin
+  if nfReadOnly in FOwner.FFlags then
+    raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'NamedNodeMap.RemoveNamedItem');
   Result := InternalRemove(name);
   if Result = nil then
     raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItem');
@@ -1544,6 +1573,8 @@ end;
 
 function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
 begin
+  if nfReadOnly in FOwner.FFlags then
+    raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'NamedNodeMap.RemoveNamedItemNS');
   // TODO: Implement TDOMNamedNodeMap.RemoveNamedItemNS
   Result := nil;
 end;
@@ -1565,6 +1596,7 @@ end;
 
 procedure TDOMCharacterData.SetNodeValue(const AValue: DOMString);
 begin
+  Changing;
   FNodeValue := AValue;
 end;
 
@@ -1577,11 +1609,13 @@ end;
 
 procedure TDOMCharacterData.AppendData(const arg: DOMString);
 begin
+  Changing;
   FNodeValue := FNodeValue + arg;
 end;
 
 procedure TDOMCharacterData.InsertData(offset: LongWord; const arg: DOMString);
 begin
+  Changing;
   if offset > Length then
     raise EDOMIndexSize.Create('CharacterData.InsertData');
   Insert(arg, FNodeValue, offset+1);
@@ -1589,6 +1623,7 @@ end;
 
 procedure TDOMCharacterData.DeleteData(offset, count: LongWord);
 begin
+  Changing;
   if offset > Length then
     raise EDOMIndexSize.Create('CharacterData.DeleteData');
   Delete(FNodeValue, offset+1, count);
@@ -1685,86 +1720,61 @@ end;
 
 destructor TDOMDocument.Destroy;
 begin
-  ClearIDList;
   FreeAndNil(FIDList);   // set to nil before starting destroying chidlren
   inherited Destroy;
 end;
 
 function TDOMDocument.AddID(Attr: TDOMAttr): Boolean;
 var
-  I: Cardinal;
-  Item: PIDItem;
+  ID: DOMString;
+  Exists: Boolean;
+  p: PHashItem;
 begin
   if FIDList = nil then
-    FIDList := TList.Create;
-  New(Item);
-  Item^.ID := Attr.Value;
-  Item^.Element := Attr.OwnerElement;
-  if not FindID(Item^.ID, I) then
+    FIDList := THashTable.Create(256, False);
+
+  ID := Attr.Value;
+  p := FIDList.FindOrAdd(DOMPChar(ID), Length(ID), Exists);
+  if not Exists then
   begin
-    FIDList.Insert(I, Item);
+    p^.Data := Attr.OwnerElement;
     Result := True;
   end
   else
-  begin
-    Dispose(Item);
     Result := False;
-  end;
 end;
 
 // This shouldn't be called if document has no IDs,
 // or when it is being destroyed
-procedure TDOMDocument.RemoveID(Elem: TDOMElement);
-var
-  I: Integer;
-begin
-  for I := 0 to FIDList.Count-1 do
-  begin
-    if PIDItem(FIDList.List^[I])^.Element = Elem then
-    begin
-      Dispose(PIDItem(FIDList.List^[I]));
-      FIDList.Delete(I);
-      Exit;
-    end;
+// TODO: This could be much faster if removing ID happens
+// upon modification of corresponding attribute value.
+
+type
+  TempRec = record
+    Element: TDOMElement;
+    Entry: PHashItem;
   end;
-end;
 
-function TDOMDocument.FindID(const aID: DOMString; out Index: LongWord): Boolean;
-var
-  L, H, I, C: Integer;
-  P: PIDItem;
+function CheckID(Entry: PHashItem; arg: Pointer): Boolean;
 begin
-  Result := False;
-  L := 0;
-  H := FIDList.Count - 1;
-  while L <= H do
+  if Entry^.Data = TempRec(arg^).Element then
   begin
-    I := (L + H) shr 1;
-    P := PIDItem(FIDList.List^[I]);
-    C := CompareDOMStrings(PWideChar(aID), PWideChar(P^.ID), Length(aID), Length(P^.ID));
-    if C > 0 then L := I + 1 else
-    begin
-      H := I - 1;
-      if C = 0 then
-      begin
-        Result := True;
-        L := I;
-      end;
-    end;
-  end;
-  Index := L;
+    TempRec(arg^).Entry := Entry;
+    Result := False;
+  end
+  else
+    Result := True;
 end;
 
-procedure TDOMDocument.ClearIDList;
+procedure TDOMDocument.RemoveID(Elem: TDOMElement);
 var
-  I: Integer;
+  hr: TempRec;
 begin
-  if Assigned(FIDList) then
-  begin
-    for I := 0 to FIDList.Count-1 do
-      Dispose(PIDItem(FIDList.List^[I]));
-    FIDList.Clear;
-  end;    
+  hr.Element := Elem;
+  hr.Entry := nil;
+  FIDList.ForEach(@CheckID, @hr);
+  if Assigned(hr.Entry) then
+    FIDList.Remove(hr.Entry);
 end;
 
 function TDOMDocument.GetNodeType: Integer;
@@ -1924,13 +1934,10 @@ begin
 end;
 
 function TDOMDocument.GetElementById(const ElementID: DOMString): TDOMElement;
-var
-  I: Cardinal;
 begin
-  if Assigned(FIDList) and FindID(ElementID, I) then
-    Result := PIDItem(FIDList.List^[I])^.Element
-  else
   Result := nil;
+  if Assigned(FIDList) then
+    Result := TDOMElement(FIDList.Get(DOMPChar(ElementID), Length(ElementID)));
 end;
 
 function TDOMDocument.ImportNode(ImportedNode: TDOMNode;
@@ -1980,6 +1987,7 @@ begin
     if Assigned(ent) then
       ent.CloneChildren(Result, Self);
   end;
+  Result.SetReadOnly(True);
 end;
 
 procedure TXMLDocument.SetXMLVersion(const aValue: DOMString);
@@ -2007,7 +2015,6 @@ begin
   // Cloned attribute is always specified and carries its children
   Result := ACloneOwner.CreateAttribute(FName);
   TDOMAttr(Result).FDataType := FDataType;
-  // Declared = ?
   CloneChildren(Result, ACloneOwner);
 end;
 
@@ -2119,6 +2126,7 @@ var
   I: Cardinal;
   attr: TDOMAttr;
 begin
+  Changing;
   if Attributes.Find(name, I) then
     Attr := FAttributes[I] as TDOMAttr
   else
@@ -2132,6 +2140,7 @@ end;
 
 procedure TDOMElement.RemoveAttribute(const name: DOMString);
 begin
+  Changing;
 // (note) NamedNodeMap.RemoveNamedItem can raise NOT_FOUND_ERR and we should not.
   if Assigned(FAttributes) then
     FAttributes.InternalRemove(name).Free;
@@ -2140,6 +2149,7 @@ end;
 procedure TDOMElement.RemoveAttributeNS(const nsURI,
   aLocalName: DOMString);
 begin
+  Changing;
   // TODO: Implement TDOMElement.RemoveAttributeNS
   raise EDOMNotSupported.Create('TDOMElement.RemoveAttributeNS');
 end;
@@ -2202,14 +2212,18 @@ end;
 
 function TDOMElement.RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
 begin
+  Changing;
   Result:=nil;
-  if FAttributes=nil then exit;
   // TODO: DOM 2: must raise NOT_FOUND_ERR if OldAttr is not ours.
   //       -- but what is the purpose of return value then?
   // TODO: delegate to TNamedNodeMap?  Nope, it does not have such method
   // (note) one way around is to remove by name
-  if FAttributes.FList.Remove(OldAttr) > -1 then
+  if Assigned(FAttributes) and (FAttributes.FList.Remove(OldAttr) > -1) then
+  begin
     Result := OldAttr;
+  end
+  else
+    raise EDOMNotFound.Create('Element.RemoveAttributeNode');
 end;
 
 function TDOMElement.GetElementsByTagName(const name: DOMString): TDOMNodeList;
@@ -2418,6 +2432,7 @@ begin
   TDOMEntity(Result).FNotationName := FNotationName;
   if deep then
     CloneChildren(Result, aCloneOwner);
+  Result.SetReadOnly(True);
 end;
 
 // -------------------------------------------------------
@@ -2466,6 +2481,7 @@ end;
 
 procedure TDOMProcessingInstruction.SetNodeValue(const AValue: DOMString);
 begin
+  Changing;
   FNodeValue := AValue;
 end;
 

+ 201 - 175
packages/fcl-xml/src/xmlread.pp

@@ -171,7 +171,6 @@ type
     FXML11Rules: Boolean;
     FSystemID: WideString;
     FPublicID: WideString;
-    FReloadHook: procedure of object;
     function GetSystemID: WideString;
     function GetPublicID: WideString;
   protected
@@ -306,6 +305,7 @@ type
     FInsideDecl: Boolean;
     FDocNotValid: Boolean;
     FValue: TWideCharBuf;
+    FEntityValue: TWideCharBuf;
     FName: TWideCharBuf;
     FTokenStart: TLocation;
     FStandalone: Boolean;          // property of Doc ?
@@ -318,6 +318,7 @@ type
     FSaViolation: Boolean;
     FDTDStartPos: PWideChar;
     FIntSubset: TWideCharBuf;
+    FAttrTag: Cardinal;
 
     FColonPos: Integer;
     FValidate: Boolean;            // parsing options, copy of FCtrl.Options
@@ -340,6 +341,7 @@ type
     procedure ParseQuantity(CP: TContentParticle);
     procedure StoreLocation(out Loc: TLocation);
     function ValidateAttrSyntax(AttrDef: TDOMAttrDef; const aValue: WideString): Boolean;
+    procedure ValidateAttrValue(Attr: TDOMAttr; const aValue: WideString);
     procedure AddForwardRef(aList: TFPList; Buf: PWideChar; Length: Integer);
     procedure ClearRefs(aList: TFPList);
     procedure ValidateIdRefs;
@@ -379,14 +381,14 @@ type
     procedure ParseDoctypeDecl;                                         // [28]
     procedure ParseMarkupDecl;                                          // [29]
     procedure ParseElement;                                             // [39]
+    procedure ParseAttribute(Elem: TDOMElement; ElDef: TDOMElementDef);
     procedure ParseContent;                                             // [43]
     function  ResolvePredefined: Boolean;
     procedure IncludeEntity(InAttr: Boolean);
     procedure StartPE;
-    function  ParseCharRef: Boolean;                                    // [66]
+    function  ParseCharRef(var ToFill: TWideCharBuf): Boolean;        // [66]
     function  ParseExternalID(out SysID, PubID: WideString;             // [75]
       SysIdOptional: Boolean): Boolean;
-    procedure ProcessTextAndRefs;
 
     procedure BadPENesting(S: TErrorSeverity = esError);
     procedure ParseEntityDecl;
@@ -425,6 +427,8 @@ type
   // Attribute/Element declarations
 
   TDOMAttrDef = class(TDOMAttr)
+  private
+    FTag: Cardinal;
   protected
     FExternallyDeclared: Boolean;
     FDefault: TAttrDefault;
@@ -432,6 +436,8 @@ type
     function AddEnumToken(Buf: DOMPChar; Len: Integer): Boolean;
     function HasEnumToken(const aValue: WideString): Boolean;
     function Clone(AElement: TDOMElement): TDOMAttr;
+  public
+    property Tag: Cardinal read FTag write FTag;
   end;
 
   TDOMElementDef = class(TDOMElement)
@@ -806,8 +812,8 @@ var
   c: WideChar;
   r: Integer;
 begin
-  if Assigned(FReloadHook) then
-    FReloadHook;
+  if DTDSubsetType = dsInternal then
+    FReader.DTDReloadHook;
   r := FBufEnd - FBuf;
   if r > 0 then
     Move(FBuf^, FBufStart^, r * sizeof(WideChar));
@@ -1260,6 +1266,8 @@ end;
 
 destructor TXMLReader.Destroy;
 begin
+  if Assigned(FEntityValue.Buffer) then
+    FreeMem(FEntityValue.Buffer);
   FreeMem(FName.Buffer);
   FreeMem(FValue.Buffer);
   if Assigned(FSource) then
@@ -1425,7 +1433,7 @@ begin
   Result := True;
 end;
 
-function TXMLReader.ParseCharRef: Boolean;           // [66]
+function TXMLReader.ParseCharRef(var ToFill: TWideCharBuf): Boolean;           // [66]
 var
   Value: Integer;
 begin
@@ -1460,15 +1468,15 @@ begin
     case Value of
       $01..$08, $0B..$0C, $0E..$1F:
         if FXML11 then
-          BufAppend(FValue, WideChar(Value))
+          BufAppend(ToFill, WideChar(Value))
         else
           FatalError('Invalid character reference');
       $09, $0A, $0D, $20..$D7FF, $E000..$FFFD:
-        BufAppend(FValue, WideChar(Value));
+        BufAppend(ToFill, WideChar(Value));
       $10000..$10FFFF:
         begin
-          BufAppend(FValue, WideChar($D7C0 + (Value shr 10)));
-          BufAppend(FValue, WideChar($DC00 xor (Value and $3FF)));
+          BufAppend(ToFill, WideChar($D7C0 + (Value shr 10)));
+          BufAppend(ToFill, WideChar($DC00 xor (Value and $3FF)));
         end;
     else
       FatalError('Invalid character reference');
@@ -1495,7 +1503,7 @@ begin
     end
     else
     begin
-      if ParseCharRef or ResolvePredefined then
+      if ParseCharRef(FValue) or ResolvePredefined then
         Continue;
       // have to insert entity or reference
       if FValue.Length > 0 then
@@ -1622,12 +1630,14 @@ begin
       SaveCursor := FCursor;
       FCursor := AEntity;         // build child node tree for the entity
       try
+        AEntity.SetReadOnly(False);
         if InAttr then
           DoParseAttValue(#0)
         else
           DoParseFragment;
         AEntity.FResolved := True;
       finally
+        AEntity.SetReadOnly(True);
         ContextPop;
         FCursor := SaveCursor;
         FValue.Length := 0;
@@ -1672,60 +1682,6 @@ begin
   FHavePERefs := True;
 end;
 
-procedure TXMLReader.ProcessTextAndRefs;
-var
-  nonWs: Boolean;
-begin
-  FValue.Length := 0;
-  nonWs := False;
-  StoreLocation(FTokenStart);
-  while (FCurChar <> '<') and (FCurChar <> #0) do
-  begin
-    if FCurChar <> '&' then
-    begin
-      if (FCurChar <> #32) and (FCurChar <> #10) and (FCurChar <> #9) and (FCurChar <> #13) then
-        nonWs := True;
-      BufAppend(FValue, FCurChar);
-      if FCurChar = '>' then
-        with FValue do
-          if (Length >= 3) and (Buffer[Length-2] = ']') and (Buffer[Length-3] = ']') then
-            FatalError('Literal '']]>'' is not allowed in text', 2);
-      GetChar;
-    end
-    else
-    begin
-      if FState <> rsRoot then
-        FatalError('Illegal at document level');
-
-      if FCurrContentType = ctEmpty then
-          ValidationError('References are illegal in EMPTY elements', []);
-
-      if ParseCharRef or ResolvePredefined then
-        nonWs := True // CharRef to whitespace is not considered whitespace
-      else
-      begin
-        if (nonWs or FPreserveWhitespace) and (FValue.Length > 0)  then
-        begin
-          // 'Reference illegal at root' is checked above, no need to check here
-          DoText(FValue.Buffer, FValue.Length, not nonWs);
-          FValue.Length := 0;
-        end;
-        IncludeEntity(False);
-      end;
-    end;
-  end; // while
-  if FState = rsRoot then
-  begin
-    if (nonWs or FPreserveWhitespace) and (FValue.Length > 0)  then
-    begin
-      DoText(FValue.Buffer, FValue.Length, not nonWs);
-      FValue.Length := 0;
-    end;
-  end
-  else if nonWs then
-    FatalError('Illegal at document level', -1);
-end;
-
 procedure TXMLReader.ExpectAttValue;    // [10]
 var
   Delim: WideChar;
@@ -1955,14 +1911,12 @@ begin
   begin
     BufAllocate(FIntSubset, 256);
     FSource.DTDSubsetType := dsInternal;
-    FSource.FReloadHook := {$IFDEF FPC}@{$ENDIF}DTDReloadHook;
     try
       FDTDStartPos := FSource.FBuf;
       ParseMarkupDecl;
       DTDReloadHook;     // fetch last chunk
       SetString(FDocType.FInternalSubset, FIntSubset.Buffer, FIntSubset.Length);
     finally
-      FSource.FReloadHook := nil;
       FreeMem(FIntSubset.Buffer);
       FSource.DTDSubsetType := dsNone;
     end;
@@ -1989,6 +1943,7 @@ begin
   end;
   FCursor := Doc;
   ValidateDTD;
+  FDocType.SetReadOnly(True);
 end;
 
 procedure TXMLReader.ExpectEq;   // [25]
@@ -2324,7 +2279,9 @@ var
   CurrentEntity: TObject;
 begin
   CurrentEntity := FSource.FEntity;
-  FValue.Length := 0;
+  if FEntityValue.Buffer = nil then
+    BufAllocate(FEntityValue, 256);
+  FEntityValue.Length := 0;
   // "Included in literal": process until delimiter hit IN SAME context
   while not ((FSource.FEntity = CurrentEntity) and CheckForChar(Delim)) do
   if CheckForChar('%') then
@@ -2337,16 +2294,16 @@ begin
   end
   else if FCurChar = '&' then  // CharRefs: include, EntityRefs: bypass
   begin
-    if not ParseCharRef then
+    if not ParseCharRef(FEntityValue) then
     begin
-      BufAppend(FValue, '&');
-      BufAppendChunk(FValue, FName.Buffer, FName.Length);
-      BufAppend(FValue, ';');
+      BufAppend(FEntityValue, '&');
+      BufAppendChunk(FEntityValue, FName.Buffer, FName.Length);
+      BufAppend(FEntityValue, ';');
     end;
   end
   else if FCurChar <> #0 then         // Regular character
   begin
-    BufAppend(FValue, FCurChar);
+    BufAppend(FEntityValue, FCurChar);
     GetChar;
   end
   else if (FSource.FEntity = CurrentEntity) or not ContextPop then         // #0
@@ -2378,6 +2335,7 @@ begin
   end;
 
   Entity := TDOMEntityEx.Create(Doc);
+  Entity.SetReadOnly(True);
   try
     Entity.FExternallyDeclared := FSource.DTDSubsetType <> dsInternal;
     Entity.FName := ExpectName;
@@ -2392,7 +2350,7 @@ begin
       StoreLocation(Entity.FStartLocation);
       if not ParseEntityDeclValue(Delim) then
         DoErrorPos(esFatal, 'Literal has no closing quote', Entity.FStartLocation);
-      SetString(Entity.FReplacementText, FValue.Buffer, FValue.Length);
+      SetString(Entity.FReplacementText, FEntityValue.Buffer, FEntityValue.Length);
     end
     else
       if not ParseExternalID(Entity.FSystemID, Entity.FPublicID, False) then
@@ -2575,6 +2533,8 @@ begin
 end;
 
 procedure TXMLReader.ParseContent;
+var
+  nonWs: Boolean;
 begin
   repeat
     if FCurChar = '<' then
@@ -2600,7 +2560,56 @@ begin
         RaiseNameNotFound;
     end
     else
-      ProcessTextAndRefs;
+    begin
+      FValue.Length := 0;
+      nonWs := False;
+      StoreLocation(FTokenStart);
+      while (FCurChar <> '<') and (FCurChar <> #0) do
+      begin
+        if FCurChar <> '&' then
+        begin
+          if (FCurChar <> #32) and (FCurChar <> #10) and (FCurChar <> #9) and (FCurChar <> #13) then
+            nonWs := True;
+          BufAppend(FValue, FCurChar);
+          if FCurChar = '>' then
+          with FValue do
+            if (Length >= 3) and (Buffer[Length-2] = ']') and (Buffer[Length-3] = ']') then
+              FatalError('Literal '']]>'' is not allowed in text', 2);
+          GetChar;
+        end
+        else
+        begin
+          if FState <> rsRoot then
+            FatalError('Illegal at document level');
+
+          if FCurrContentType = ctEmpty then
+            ValidationError('References are illegal in EMPTY elements', []);
+
+          if ParseCharRef(FValue) or ResolvePredefined then
+            nonWs := True // CharRef to whitespace is not considered whitespace
+          else
+          begin
+            if (nonWs or FPreserveWhitespace) and (FValue.Length > 0)  then
+            begin
+              // 'Reference illegal at root' is checked above, no need to check here
+              DoText(FValue.Buffer, FValue.Length, not nonWs);
+              FValue.Length := 0;
+            end;
+            IncludeEntity(False);
+          end;
+        end;
+      end; // while
+      if FState = rsRoot then
+      begin
+        if (nonWs or FPreserveWhitespace) and (FValue.Length > 0)  then
+        begin
+          DoText(FValue.Buffer, FValue.Length, not nonWs);
+          FValue.Length := 0;
+        end;
+      end
+      else if nonWs then
+        FatalError('Illegal at document level', -1);
+    end;
   until FCurChar = #0;
 end;
 
@@ -2610,8 +2619,6 @@ var
   NewElem: TDOMElement;
   ElDef: TDOMElementDef;
   IsEmpty: Boolean;
-  attr: TDOMAttr;
-  OldAttr: TDOMNode;
 begin
   if FState > rsRoot then
     FatalError('Only one top-level element allowed', FName.Length)
@@ -2624,6 +2631,8 @@ begin
 
   NewElem := doc.CreateElementBuf(FName.Buffer, FName.Length);
   FCursor.AppendChild(NewElem);
+  // we're about to process a new set of attributes
+  Inc(FAttrTag);
 
   // Find declaration for this element
   ElDef := nil;
@@ -2639,28 +2648,15 @@ begin
     ValidationError('Element ''%s'' is not allowed in this context',[NewElem.TagName], FName.Length);
 
   IsEmpty := False;
-  if SkipS then
+  while (FSource.FBuf^ <> '>') and (FSource.FBuf^ <> '/') do
   begin
-    while (FCurChar <> '>') and (FCurChar <> '/') do
-    begin
-      CheckName;
-      attr := doc.CreateAttributeBuf(FName.Buffer, FName.Length);
-
-      // !!cannot use TDOMElement.SetAttributeNode because it will free old attribute
-      OldAttr := NewElem.Attributes.SetNamedItem(Attr);
-      if Assigned(OldAttr) then
-      begin
-        OldAttr.Free;
-        FatalError('Duplicate attribute', FName.Length);
-      end;
-      ExpectEq;
-      FCursor := attr;
-      ExpectAttValue;
-      if (FCurChar <> '>') and (FCurChar <> '/') then
-        SkipS(True);
-    end;   // while
+    SkipS(True);
+    if (FSource.FBuf^ = '>') or (FSource.FBuf^ = '/') then
+      Break;
+    ParseAttribute(NewElem, ElDef);
   end;
-  if FCurChar = '/' then
+
+  if FSource.FBuf^ = '/' then
   begin
     IsEmpty := True;
     GetChar;
@@ -2706,15 +2702,80 @@ begin
   PopVC;
 end;
 
+procedure TXMLReader.ParseAttribute(Elem: TDOMElement; ElDef: TDOMElementDef);
+var
+  attr: TDOMAttr;
+  AttDef: TDOMAttrDef;
+  OldAttr: TDOMNode;
+
+procedure CheckValue;
+var
+  AttValue, OldValue: WideString;
+begin
+  if FStandalone and AttDef.FExternallyDeclared then
+  begin
+    OldValue := Attr.Value;
+    TDOMAttrDef(Attr).FDataType := AttDef.FDataType;
+    AttValue := Attr.Value;
+    if AttValue <> OldValue then
+      StandaloneError(-1);
+  end
+  else
+  begin
+    TDOMAttrDef(Attr).FDataType := AttDef.FDataType;
+    AttValue := Attr.Value;
+  end;
+  // TODO: what about normalization of AttDef.Value? (Currently it IS normalized)
+  if (AttDef.FDefault = adFixed) and (AttDef.Value <> AttValue) then
+    ValidationError('Value of attribute ''%s'' does not match its #FIXED default',[AttDef.Name], -1);
+  if not ValidateAttrSyntax(AttDef, AttValue) then
+    ValidationError('Attribute ''%s'' type mismatch', [AttDef.Name], -1);
+  ValidateAttrValue(Attr, AttValue);
+end;
+
+begin
+  CheckName;
+  attr := doc.CreateAttributeBuf(FName.Buffer, FName.Length);
+
+  if Assigned(ElDef) then
+  begin
+    AttDef := TDOMAttrDef(ElDef.GetAttributeNode(attr.Name));
+    if AttDef = nil then
+      ValidationError('Using undeclared attribute ''%s'' on element ''%s''',[attr.Name, Elem.TagName], FName.Length)
+    else
+      AttDef.Tag := FAttrTag;  // indicates that this one is specified
+  end
+  else
+    AttDef := nil;
+
+  // !!cannot use TDOMElement.SetAttributeNode because it will free old attribute
+  OldAttr := Elem.Attributes.SetNamedItem(Attr);
+  if Assigned(OldAttr) then
+  begin
+    OldAttr.Free;
+    FatalError('Duplicate attribute', FName.Length);
+  end;
+  ExpectEq;
+  FCursor := attr;
+  ExpectAttValue;
+
+  if Assigned(AttDef) and ((AttDef.FDataType <> dtCdata) or (AttDef.FDefault = adFixed)) then
+    CheckValue;
+end;
+
 procedure TXMLReader.AddForwardRef(aList: TFPList; Buf: PWideChar; Length: Integer);
 var
   w: PForwardRef;
 begin
   New(w);
   SetString(w^.Value, Buf, Abs(Length));
-  StoreLocation(w^.Loc);
   if Length > 0 then
+  begin
+    StoreLocation(w^.Loc);
     Dec(w^.Loc.LinePos, Length);
+  end
+  else
+    w^.Loc := FTokenStart;
   aList.Add(w);
 end;
 
@@ -2745,9 +2806,7 @@ var
 
 procedure DoDefaulting;
 var
-  AttValue: WideString;
-  I, L, StartPos, EndPos: Integer;
-  Entity: TDOMEntity;
+  I: Integer;
   AttDef: TDOMAttrDef;
 begin
   Map := ElDef.FAttributes;
@@ -2756,96 +2815,25 @@ begin
   begin
     AttDef := Map[I] as TDOMAttrDef;
 
-    Attr := Element.GetAttributeNode(AttDef.Name);
-    if Attr = nil then
+    if AttDef.Tag <> FAttrTag then  // this one wasn't specified
     begin
-      // attribute needs defaulting
       case AttDef.FDefault of
         adDefault, adFixed: begin
           if FStandalone and AttDef.FExternallyDeclared then
             StandaloneError;
           Attr := AttDef.Clone(Element);
           Element.SetAttributeNode(Attr);
+          ValidateAttrValue(Attr, Attr.Value);
         end;
         adRequired:  ValidationError('Required attribute ''%s'' of element ''%s'' is missing',[AttDef.Name, Element.TagName], 0)
       end;
-    end
-    else
-    begin
-      TDOMAttrDef(Attr).FDeclared := True;
-      // bypass heavyweight operations if possible
-      if (AttDef.DataType <> dtCdata) or (AttDef.FDefault = adFixed) then
-      begin
-        AttValue := Attr.Value; // unnormalized
-        // now assign DataType so that value is correctly normalized
-        TDOMAttrDef(Attr).FDataType := AttDef.FDataType;
-        if FStandalone and AttDef.FExternallyDeclared and (Attr.Value <> AttValue) then
-          StandaloneError;
-        AttValue := Attr.Value; // recalculate
-        // TODO: what about normalization of AttDef.Value? (Currently it IS normalized)
-        if (AttDef.FDefault = adFixed) and (AttDef.Value <> AttValue) then
-          ValidationError('Value of attribute ''%s'' does not match its #FIXED default',[AttDef.Name], 0);
-        if not ValidateAttrSyntax(AttDef, AttValue) then
-          ValidationError('Attribute ''%s'' type mismatch', [AttDef.Name], 0);
-      end;
     end;
-
-    if Attr = nil then
-      Continue;
-    L := Length(AttValue);
-    case Attr.DataType of
-      dtId: if not Doc.AddID(Attr) then
-              ValidationError('The ID ''%s'' is not unique', [AttValue], 0);
-
-      dtIdRef, dtIdRefs: begin
-        StartPos := 1;
-        while StartPos <= L do
-        begin
-          EndPos := StartPos;
-          while (EndPos <= L) and (AttValue[EndPos] <> #32) do
-            Inc(EndPos);
-          // pass negative Length, so current location is not altered
-          AddForwardRef(FIDRefs, @AttValue[StartPos], StartPos-EndPos);
-          StartPos := EndPos + 1;
-        end;
-      end;
-
-      dtEntity, dtEntities: begin
-        StartPos := 1;
-        while StartPos <= L do
-        begin
-          EndPos := StartPos;
-          while (EndPos <= L) and (AttValue[EndPos] <> #32) do
-            Inc(EndPos);
-          Entity := TDOMEntity(FDocType.Entities.GetNamedItem(Copy(AttValue, StartPos, EndPos-StartPos)));
-          if (Entity = nil) or (Entity.NotationName = '') then
-            ValidationError('Attribute ''%s'' type mismatch', [Attr.Name], 0);
-          StartPos := EndPos + 1;
-        end;
-      end;
-    end;
-  end;
-end;
-
-procedure ReportUndeclared;
-var
-  I: Integer;
-begin
-  Map := Element.Attributes;
-  for I := 0 to Map.Length-1 do
-  begin
-    Attr := TDOMAttr(Map[I]);
-    if not TDOMAttrDef(Attr).FDeclared then
-      ValidationError('Using undeclared attribute ''%s'' on element ''%s''',[Attr.Name, Element.TagName], 0);
   end;
 end;
 
 begin
   if Assigned(ElDef) and Assigned(ElDef.FAttributes) then
     DoDefaulting;
-  // Now report undeclared attributes
-  if Assigned(FDocType) and Element.HasAttributes then
-    ReportUndeclared;
 end;
 
 function TXMLReader.ParseExternalID(out SysID, PubID: WideString;     // [75]
@@ -2888,6 +2876,45 @@ begin
   end;
 end;
 
+procedure TXMLReader.ValidateAttrValue(Attr: TDOMAttr; const aValue: WideString);
+var
+  L, StartPos, EndPos: Integer;
+  Entity: TDOMEntity;
+begin
+  L := Length(aValue);
+  case Attr.DataType of
+    dtId: if not Doc.AddID(Attr) then
+            ValidationError('The ID ''%s'' is not unique', [aValue], -1);
+
+    dtIdRef, dtIdRefs: begin
+      StartPos := 1;
+      while StartPos <= L do
+      begin
+        EndPos := StartPos;
+        while (EndPos <= L) and (aValue[EndPos] <> #32) do
+          Inc(EndPos);
+        // pass negative length, so uses FTokenStart as location
+        AddForwardRef(FIDRefs, @aValue[StartPos], StartPos-EndPos);
+        StartPos := EndPos + 1;
+      end;
+    end;
+
+    dtEntity, dtEntities: begin
+      StartPos := 1;
+      while StartPos <= L do
+      begin
+        EndPos := StartPos;
+        while (EndPos <= L) and (aValue[EndPos] <> #32) do
+          Inc(EndPos);
+        Entity := TDOMEntity(FDocType.Entities.GetNamedItem(Copy(aValue, StartPos, EndPos-StartPos)));
+        if (Entity = nil) or (Entity.NotationName = '') then
+          ValidationError('Attribute ''%s'' type mismatch', [Attr.Name], -1);
+        StartPos := EndPos + 1;
+      end;
+    end;
+  end;
+end;
+
 procedure TXMLReader.ValidateRoot;
 begin
   if Assigned(FDocType) then
@@ -3061,7 +3088,6 @@ begin
   Result := TDOMAttr.Create(FOwnerDocument);
   TDOMAttrEx(Result).FName := Self.FName;
   TDOMAttrEx(Result).FDataType := FDataType;
-  TDOMAttrEx(Result).FDeclared := True;
   CloneChildren(Result, FOwnerDocument);
 end;
 

+ 221 - 0
packages/fcl-xml/src/xmlutils.pp

@@ -30,6 +30,42 @@ function IsXmlNmTokens(const Value: WideString; Xml11: Boolean = False): Boolean
 function IsValidXmlEncoding(const Value: WideString): Boolean;
 function Xml11NamePages: PByteArray;
 procedure NormalizeSpaces(var Value: WideString);
+function Hash(InitValue: LongWord; Key: PWideChar; KeyLen: Integer): LongWord;
+
+{ a simple hash table with WideString keys }
+
+type
+  PPHashItem = ^PHashItem;
+  PHashItem = ^THashItem;
+  THashItem = record
+    Key: WideString;
+    HashValue: LongWord;
+    Next: PHashItem;
+    Data: TObject;
+  end;
+
+  THashForEach = function(Entry: PHashItem; arg: Pointer): Boolean;
+
+  THashTable = class(TObject)
+  private
+    FCount: LongWord;
+    FBucketCount: LongWord;
+    FBucket: PPHashItem;
+    FOwnsObjects: Boolean;
+    function Lookup(Key: PWideChar; KeyLength: Integer; var Found: Boolean; CanCreate: Boolean): PHashItem;
+    procedure Resize(NewCapacity: LongWord);
+  public
+    constructor Create(InitSize: Integer; OwnObjects: Boolean);
+    destructor Destroy; override;
+    procedure Clear;
+    function Find(Key: PWideChar; KeyLen: Integer): PHashItem;
+    function FindOrAdd(Key: PWideChar; KeyLen: Integer; var Found: Boolean): PHashItem; overload;
+    function FindOrAdd(Key: PWideChar; KeyLen: Integer): PHashItem; overload;
+    function Get(Key: PWideChar; KeyLen: Integer): TObject;
+    function Remove(Entry: PHashItem): Boolean;
+    procedure ForEach(proc: THashForEach; arg: Pointer);
+    property Count: LongWord read FCount;
+  end;
 
 {$i names.inc}
 
@@ -239,6 +275,191 @@ begin
   end;
 end;
 
+function Hash(InitValue: LongWord; Key: PWideChar; KeyLen: Integer): LongWord;
+begin
+  Result := InitValue;
+  while KeyLen <> 0 do
+  begin
+    Result := Result * $F4243 xor ord(Key^);
+    Inc(Key);
+    Dec(KeyLen);
+  end;
+end;
+
+function KeyCompare(const Key1: WideString; Key2: Pointer; Key2Len: Integer): Boolean;
+begin
+  Result := (Length(Key1)=Key2Len) and (CompareWord(Pointer(Key1)^, Key2^, Key2Len) = 0);
+end;
+
+{ THashTable }
+
+constructor THashTable.Create(InitSize: Integer; OwnObjects: Boolean);
+var
+  I: Integer;
+begin
+  inherited Create;
+  FOwnsObjects := OwnObjects;
+  I := 256;
+  while I < InitSize do I := I shl 1;
+  FBucketCount := I;
+  FBucket := AllocMem(I * sizeof(PHashItem));
+end;
+
+destructor THashTable.Destroy;
+begin
+  Clear;
+  FreeMem(FBucket);
+  inherited Destroy;
+end;
+
+procedure THashTable.Clear;
+var
+  I: Integer;
+  item, next: PHashItem;
+begin
+  for I := 0 to FBucketCount-1 do
+  begin
+    item := FBucket[I];
+    while Assigned(item) do
+    begin
+      next := item^.Next;
+      if FOwnsObjects then
+        item^.Data.Free;
+      Dispose(item);
+      item := next;
+    end;
+  end;
+  FillChar(FBucket^, FBucketCount * sizeof(PHashItem), 0);
+end;
+
+function THashTable.Find(Key: PWideChar; KeyLen: Integer): PHashItem;
+var
+  Dummy: Boolean;
+begin
+  Result := Lookup(Key, KeyLen, Dummy, False);
+end;
+
+function THashTable.FindOrAdd(Key: PWideChar; KeyLen: Integer;
+  var Found: Boolean): PHashItem;
+begin
+  Result := Lookup(Key, KeyLen, Found, True);
+end;
+
+function THashTable.FindOrAdd(Key: PWideChar; KeyLen: Integer): PHashItem;
+var
+  Dummy: Boolean;
+begin
+  Result := Lookup(Key, KeyLen, Dummy, True);
+end;
+
+function THashTable.Get(Key: PWideChar; KeyLen: Integer): TObject;
+var
+  e: PHashItem;
+  Dummy: Boolean;
+begin
+  e := Lookup(Key, KeyLen, Dummy, False);
+  if Assigned(e) then
+    Result := e^.Data
+  else
+    Result := nil;  
+end;
+
+function THashTable.Lookup(Key: PWideChar; KeyLength: Integer;
+  var Found: Boolean; CanCreate: Boolean): PHashItem;
+var
+  Entry: PPHashItem;
+  h: LongWord;
+begin
+  h := Hash(0, Key, KeyLength);
+  Entry := @FBucket[h mod FBucketCount];
+  while Assigned(Entry^) and not ((Entry^^.HashValue = h) and KeyCompare(Entry^^.Key, Key, KeyLength) ) do
+    Entry := @Entry^^.Next;
+  Found := Assigned(Entry^);
+  if Found or (not CanCreate) then
+  begin
+    Result := Entry^;
+    Exit;
+  end;
+  if FCount > FBucketCount then  { arbitrary limit, probably too high }
+  begin
+    Resize(FBucketCount * 2);
+    Result := Lookup(Key, KeyLength, Found, CanCreate);
+  end
+  else
+  begin
+    New(Result);
+    SetString(Result^.Key, Key, KeyLength);
+    Result^.HashValue := h;
+    Result^.Data := nil;
+    Result^.Next := nil;
+    Inc(FCount);
+    Entry^ := Result;
+  end;
+end;
+
+procedure THashTable.Resize(NewCapacity: LongWord);
+var
+  p, chain: PPHashItem;
+  i: Integer;
+  e, n: PHashItem;
+begin
+  p := AllocMem(NewCapacity * sizeof(PHashItem));
+  for i := 0 to FBucketCount-1 do
+  begin
+    e := FBucket[i];
+    while Assigned(e) do
+    begin
+      chain := @p[e^.HashValue mod NewCapacity];
+      n := e^.Next;
+      e^.Next := chain^;
+      chain^ := e;
+      e := n;
+    end;
+  end;
+  FBucketCount := NewCapacity;
+  FreeMem(FBucket);
+  FBucket := p;
+end;
+
+function THashTable.Remove(Entry: PHashItem): Boolean;
+var
+  chain: PPHashItem;
+begin
+  chain := @FBucket[Entry^.HashValue mod FBucketCount];
+  while Assigned(chain^) do
+  begin
+    if chain^ = Entry then
+    begin
+      chain^ := Entry^.Next;
+      if FOwnsObjects then
+        Entry^.Data.Free;
+      Dispose(Entry);
+      Dec(FCount);
+      Result := True;
+      Exit;
+    end;
+    chain := @chain^^.Next;
+  end;
+  Result := False;
+end;
+
+procedure THashTable.ForEach(proc: THashForEach; arg: Pointer);
+var
+  i: Integer;
+  e: PHashItem;
+begin
+  for i := 0 to FBucketCount-1 do
+  begin
+    e := FBucket[i];
+    while Assigned(e) do
+    begin
+      if not proc(e, arg) then
+        Exit;
+      e := e^.Next;
+    end;
+  end;
+end;
+
 initialization
 
 finalization

+ 6 - 0
packages/fcl-xml/tests/domunit.pp

@@ -62,6 +62,7 @@ type
 
 procedure _append(var coll: _collection; const Value: DOMString);
 procedure _assign(out rslt: _collection; const value: array of DOMString);
+function IsSame(exp, act: TDOMNode): Boolean;
 
 implementation
 
@@ -86,6 +87,11 @@ begin
     rslt[I] := value[I];
 end;
 
+function IsSame(exp, act: TDOMNode): Boolean;
+begin
+  Result := exp = act;
+end;
+
 procedure TDOMTestBase.SetUp;
 begin
   FParser := TDOMParser.Create;

+ 69 - 34
packages/fcl-xml/tests/testgen.pp

@@ -29,7 +29,7 @@ var
 
 function PascalType(const s: WideString): string;
 begin
-  if (s = 'DOMString') or (s = 'boolean') or (s = 'DOMError') then
+  if (s = 'DOMString') or (s = 'boolean') or (s = 'DOMError') or (s = 'double') then
     result := s
   else if s = 'int' then
     result := 'Integer'
@@ -39,7 +39,7 @@ begin
     result := '_collection'
   else if s = 'List' then
     result := '_list'
-  else if Pos(WideString('DOM'), s) = 1 then
+  else if (Pos(WideString('DOM'), s) = 1) or (Pos(WideString('XPath'), s) = 1) then
     result := 'T' + s
   else
     result := 'TDOM'+s;
@@ -147,6 +147,11 @@ begin
     else
       r := 'bad_condition(''contains intf=' + e['interface'] + ''')';
   end
+  else if e.TagName = 'same' then
+  begin
+  // maybe it would be sufficient to just compare pointers, but let's emit a helper for now
+    r := 'IsSame('+ e['expected'] + ', ' + e['actual'] + ')';
+  end
   else if e.TagName = 'not' then
   begin
     child := e.FirstChild;
@@ -304,6 +309,10 @@ begin
 
   s := node.TagName;
   apinode := api.GetElementById(s);
+  // If not found by name only, try prepending the interface name.
+  // This enables support of same-named methods with different param lists on different objects
+  if (apinode = nil) and node.HasAttribute('interface') then
+    apinode := api.GetElementById(node['interface'] + '.' + s);
   if assigned(apinode) then
   begin
     // handle most of DOM API in consistent way
@@ -369,9 +378,15 @@ begin
   // service (non-DOM) statements follow
   
   else if s = 'append' then
-    rslt.Add(indent + '_append(' + node['collection'] + ', ' + node['item'] + ');')
+    rslt.Add(indent + '_append(' + node['collection'] + ', ' + ReplaceQuotes(node['item']) + ');')
   else if s = 'assign' then
-    rslt.Add(indent + '_assign(' + node['var'] + ', ' + node['value'] + ');')
+  begin
+    cond := TypeOfVar(node['var']);
+    if (cond = '_collection') or (cond = '_list') then
+      rslt.Add(indent + '_assign(' + node['var'] + ', ' + node['value'] + ');')
+    else // emit an assignment operator. Force type for the case where they assign Document to Element.
+      rslt.Add(indent + node['var'] + ' := ' + TypeOfVar(node['var']) + '(' + ReplaceQuotes(node['value']) + ');');
+  end  
   else if s = 'increment' then
     rslt.Add(indent + 'Inc(' + node['var'] + ', ' + node['value'] + ');')
   else if s = 'decrement' then
@@ -433,6 +448,10 @@ begin
     rslt.Add(indent + 'Load('+node['var']+', '''+ node['href']+''');')
   else if s = 'implementationAttribute' then
     rslt.Add(indent + s + '[''' + node['name'] + '''] := ' + node['value'] + ';')
+  else if s = 'createXPathEvaluator' then
+    rslt.Add(indent + node['var'] + ' := CreateXPathEvaluator(' + node['document'] + ');')
+  else if s = 'comment' then
+    rslt.Add(indent + '{ Source comment: ' + node.TextContent + ' }')
   else
   begin
     if not FailFlag then
@@ -442,12 +461,44 @@ begin
   end;
 end;
 
+procedure ConvertException(el: TDOMElement; const ExceptClass: string; indent: string);
+var
+  excode: string;
+begin
+  if not SuccessVarFlag then
+    rslt.Insert(2, '  success: Boolean;');
+  SuccessVarFlag := True;
+  rslt.Add(indent+'success := False;');
+  rslt.Add(indent+'try');
+  child := el.FirstChild;
+  while assigned(child) do
+  begin
+    if child.nodeType = ELEMENT_NODE then
+    begin
+      excode := child.nodeName;
+      subchild := child.FirstChild;
+      while Assigned(subchild) do
+      begin
+        if subchild.nodeType = ELEMENT_NODE then
+          ConvertStatement(TDOMElement(subchild), indent + '  ');
+        subchild := subchild.NextSibling;
+      end;
+    end;
+    child := child.NextSibling;
+  end;
+  rslt.Add(indent+'except');
+  rslt.Add(indent+'  on E: Exception do');
+  rslt.Add(indent+'    success := (E is ' + ExceptClass +') and (' + ExceptClass + '(E).Code = ' + excode + ');');
+  rslt.Add(indent+'end;');
+  rslt.Add(indent+'AssertTrue('''+el['id']+''', success);');
+end;
+
 procedure ConvertBlock(el: TDOMNode; indent: string);
 var
   curr: TDOMNode;
   element: TDOMElement;
   List: TList;
-  cond, excode: string;
+  cond: string;
   Frag: TDOMDocumentFragment;
   I: Integer;
   ElseNode: TDOMNode;
@@ -467,34 +518,9 @@ begin
     element := TDOMElement(curr);
     n := element.TagName;
     if n = 'assertDOMException' then
-    begin
-      if not SuccessVarFlag then
-        rslt.Insert(2, '  success: Boolean;');
-      SuccessVarFlag := True;
-      rslt.Add(indent+'success := False;');
-      rslt.Add(indent+'try');
-      child := curr.FirstChild;
-      while assigned(child) do
-      begin
-        if child.nodeType = ELEMENT_NODE then
-        begin
-          excode := child.nodeName;
-          subchild := child.FirstChild;
-          while Assigned(subchild) do
-          begin
-            if subchild.nodeType = ELEMENT_NODE then
-              ConvertStatement(TDOMElement(subchild), indent + '  ');
-            subchild := subchild.NextSibling;
-          end;
-        end;
-        child := child.NextSibling;
-      end;
-      rslt.Add(indent+'except');
-      rslt.Add(indent+'  on E: Exception do');
-      rslt.Add(indent+'    success := (E is EDOMError) and (EDOMError(E).Code = ' + excode + ');');
-      rslt.Add(indent+'end;');
-      rslt.Add(indent+'AssertTrue('''+element['id']+''', success);');
-    end
+      ConvertException(element, 'EDOMError', indent)
+    else if n = 'assertXPathException' then
+      ConvertException(element, 'EXPathException', indent)
     else if n = 'try' then
     begin
       GetChildElements(curr, List);
@@ -658,7 +684,11 @@ begin
         try
           if subvars.Count > 0 then
           begin
-            TypedConsts.Add('  ' + Node['name'] + ': array[0..' + IntToStr(subvars.Count-1) + '] of DOMString = (');
+            if TDOMElement(subvars[0]).HasAttribute('type') then
+              hs := PascalType(TDOMElement(subvars[0]).GetAttribute('type'))
+            else
+              hs := 'DOMString';
+            TypedConsts.Add('  ' + Node['name'] + ': array[0..' + IntToStr(subvars.Count-1) + '] of ' + hs + ' = (');
             for J := 0 to subvars.Count-1 do
             begin
               hs := '    ' + ReplaceQuotes(subvars[J].TextContent);
@@ -817,7 +847,12 @@ begin
       if root['name'] = 'attrname' then
         root['name'] := 'attr_name';
       sl.Add('procedure ' + class_name + '.' + root['name'] + ';');
+      try
       ConvertTest(root, sl);
+      except
+        Writeln('An exception occured while converting '+root['name']);
+        raise;
+      end;
       if sl.Count > 0 then
       begin
         all.add('    procedure '+root['name']+';');

+ 7 - 0
packages/httpd20/src/apr/apr.pas

@@ -90,10 +90,16 @@ type
   pid_t = Integer;
   Ppid_t = ^pid_t;
   apr_uint16_t = Word;
+  papr_uint16_t = ^apr_uint16_t;
   apr_uint32_t = Cardinal;
+  papr_uint32_t = ^apr_uint32_t;
   apr_int64_t = Int64;
+  papr_int64_t = ^apr_int64_t;
   apr_uint64_t = Int64;
+  papr_uint64_t = ^apr_uint64_t;
   apr_socklen_t = Integer;
+  apr_byte_t = byte;
+  papr_byte_t = apr_byte_t;
   
   apr_uint32_tso_handle_t = cuint;
 
@@ -191,6 +197,7 @@ type
 {$include apr_lib.inc}
 {$include apr_signal.inc}
 {$include apr_network_io.inc}
+{$include apr_hash.inc}
 {.$include apr_portable.inc}
 
 {.$include ../aprutils/apr_uri.inc}

+ 5 - 0
packages/httpd22/src/apr/apr.pas

@@ -89,10 +89,14 @@ type
   Ppid_t = ^pid_t;
   apr_uint16_t = Word;
   apr_uint32_t = Cardinal;
+  papr_uint32_t = ^apr_uint32_t;
   apr_int64_t = Int64;
+  papr_int64_t = ^apr_int64_t;
   apr_uint64_t = Int64;
+  papr_uint64_t = ^apr_uint64_t;
   apr_socklen_t = Integer;
   apr_byte_t = Byte;
+  papr_byte_t = ^apr_byte_t;
 
   apr_uint32_tso_handle_t = cuint;
 
@@ -181,6 +185,7 @@ type
 {$include apr_lib.inc}
 {$include apr_signal.inc}
 {$include apr_network_io.inc}
+{$include apr_hash.inc}
 {.$include apr_portable.inc}
 
 {.$include ../aprutils/apr_uri.inc}

+ 28 - 6
rtl/objpas/sysutils/dati.inc

@@ -516,15 +516,37 @@ begin
 end ;
 
 {   StrToDateTime converts the string S to a TDateTime value
-    if S does not represent a valid date and time value
+    if S does not represent a valid date and/or time value
     an EConvertError will be raised   }
 
 function StrToDateTime(const s: string): TDateTime;
-var i: integer;
-begin
-i := pos(' ', s);
-if i > 0 then result := ComposeDateTime(StrToDate(Copy(S, 1, i - 1)), StrToTime(Copy(S, i + 1, length(S))))
-else result := StrToDate(S);
+var
+  i, j, k, l: integer;
+  sd, st: string;
+begin
+  l := Length(s);
+  i := 1;
+  while (i <= l) and (s[i] = ' ') do
+    Inc(i);
+  j := i;
+  while (j <= l) and (s[j] <> ' ') do
+    Inc(j);
+  k := j;
+  while (k <= l) and (s[k] = ' ') do
+    Inc(k);
+  sd := Copy(s, i, j - i);
+  st := Copy(s, k, l);
+  if (st = '') and (Pos(TimeSeparator, sd) > 0) then
+  begin
+    st := sd;
+    sd := '';
+  end;
+  if (sd <> '') and (st <> '') then
+    Result := ComposeDateTime(StrToDate(sd), StrToTime(st))
+  else if st = '' then
+    Result := StrToDate(sd)
+  else
+    Result := StrToTime(st);
 end ;
 
 {   FormatDateTime formats DateTime to the given format string FormatStr   }

+ 3 - 3
rtl/objpas/sysutils/sysstr.inc

@@ -783,7 +783,7 @@ begin
   result:=IntToHex(Int64(Value),Digits);
 end;
 
-function TryStrToInt(const s: string; var i : integer) : boolean;
+function TryStrToInt(const s: string; out i : integer) : boolean;
 var Error : word;
 begin
   Val(s, i, Error);
@@ -809,7 +809,7 @@ begin
 end;
 
 
-function TryStrToInt64(const s: string; var i : int64) : boolean;
+function TryStrToInt64(const s: string; Out i : int64) : boolean;
 var Error : word;
 begin
   Val(s, i, Error);
@@ -825,7 +825,7 @@ begin
 end;
 
 
-function TryStrToQWord(const s: string; var Q: QWord): boolean;
+function TryStrToQWord(const s: string; Out Q: QWord): boolean;
 var Error : word;
 begin
   Val(s, Q, Error);

+ 3 - 3
rtl/objpas/sysutils/sysstrh.inc

@@ -109,11 +109,11 @@ function IntToHex(Value: integer; Digits: integer): string;
 function IntToHex(Value: Int64; Digits: integer): string;
 function IntToHex(Value: QWord; Digits: integer): string;
 function StrToInt(const s: string): integer;
-function TryStrToInt(const s: string; var i : integer) : boolean;
+function TryStrToInt(const s: string; Out i : integer) : boolean;
 function StrToInt64(const s: string): int64;
-function TryStrToInt64(const s: string; var i : int64) : boolean;
+function TryStrToInt64(const s: string; Out i : int64) : boolean;
 function StrToQWord(const s: string): QWord;
-function TryStrToQWord(const s: string; var Q : QWord) : boolean;
+function TryStrToQWord(const s: string; Out Q : QWord) : boolean;
 function StrToIntDef(const S: string; Default: integer): integer;
 function StrToInt64Def(const S: string; Default: int64): int64;
 function StrToQWordDef(const S: string; Default: QWord): QWord;

+ 12 - 0
rtl/unix/bunxovl.inc

@@ -110,6 +110,18 @@ begin
   FpStat:=FpStat(pchar(path),buf);
 End;
 
+Function  fpLstat   (path:pchar;var Info:stat):cint;
+
+begin
+  fpLstat:=fplstat(path,@info);
+end;
+
+Function  fpLstat   (Filename: ansistring;var Info:stat):cint;
+
+begin
+  fpLstat:=fplstat(filename,@info);
+end;
+
 Function FpAccess (pathname : AnsiString; aMode : cInt): cInt; {$ifdef VER2_0}inline;{$endif}
 Begin
   FpAccess:=FpAccess(pchar(pathname),amode);

+ 2 - 0
rtl/unix/bunxovlh.inc

@@ -37,6 +37,8 @@ Function  FpRmdir   (path : AnsiString): cInt; inline;
 Function  FpRename  (old  : AnsiString;newpath: AnsiString): cInt; inline;
 Function  FpStat    (path: AnsiString; var buf : stat): cInt; inline;
 Function  FpStat    (path: String; var buf : stat): cInt;
+Function  fpLstat   (path:pchar;var Info:stat):cint;
+Function  fpLstat   (Filename: ansistring;var Info:stat):cint;
 Function  FpAccess  (pathname : AnsiString; aMode : cInt): cInt; inline;
 function  FpWaitPid (pid : TPid; Var Status : cInt; Options : cint) : TPid;
 

+ 19 - 6
rtl/unix/sysutils.pp

@@ -292,6 +292,10 @@ end;
 
 Function LinuxToWinAttr (FN : Pchar; Const Info : Stat) : Longint;
 
+Var
+  FNL : String;
+  LinkInfo : Stat;
+
 begin
   Result:=faArchive;
   If fpS_ISDIR(Info.st_mode) then
@@ -303,7 +307,13 @@ begin
   If fpS_ISSOCK(Info.st_mode) or fpS_ISBLK(Info.st_mode) or fpS_ISCHR(Info.st_mode) or fpS_ISFIFO(Info.st_mode) Then
      Result:=Result or faSysFile;
   If fpS_ISLNK(Info.st_mode) Then
+    begin
     Result:=Result or faSymLink;
+    // Windows reports if the link points to a directory.
+    FNL:=StrPas(FN);
+    if (fpstat(FNL,LinkInfo)>=0) and fpS_ISDIR(LinkInfo.st_mode) then
+      Result := Result or faDirectory;
+    end;
 end;
 
 
@@ -422,10 +432,15 @@ Function FindGetFileInfo(const s:string;var f:TSearchRec):boolean;
 var
   st      : baseunix.stat;
   WinAttr : longint;
+  
 begin
   FindGetFileInfo:=false;
-  if not fpstat(pointer(s),st)>=0 then
-   exit;
+  If Assigned(F.FindHandle) and ((((PUnixFindData(f.FindHandle)^.searchattr)) and faSymlink) > 0) then
+    FindGetFileInfo:=(fplstat(pointer(s),st)=0)    
+  else
+    FindGetFileInfo:=(fpstat(pointer(s),st)=0);
+  If not FindGetFileInfo then 
+    exit;  
   WinAttr:=LinuxToWinAttr(PChar(pointer(s)),st);
   If (f.FindHandle = nil) or ((WinAttr and Not(PUnixFindData(f.FindHandle)^.searchattr))=0) Then
    Begin
@@ -441,12 +456,10 @@ end;
 
 Function FindNext (Var Rslt : TSearchRec) : Longint;
 {
-  re-opens dir if not already in array and calls FindWorkProc
+  re-opens dir if not already in array and calls FindGetFileInfo
 }
 Var
   DirName  : String;
-  i,
-  ArrayPos : Longint;
   FName,
   SName    : string;
   Found,
@@ -497,7 +510,7 @@ End;
 
 Function FindFirst (Const Path : String; Attr : Longint; out Rslt : TSearchRec) : Longint;
 {
-  opens dir and calls FindWorkProc
+  opens dir and calls FindNext if needed.
 }
 var
   UnixFindData : PUnixFindData;