Newsletter #9: Jul/Aug/Sep 2002 Edition
Home Up Feedback Search

Products
Order
Downloads
Support
Articles & Tips
Recommended Links
About

 
 

 
 
RiverSoftAVG Newsletter #9
July/Aug/Sep 2002

Hello and welcome back for the 9th edition of the RiverSoftAVG newsletter!  It has been a busy but satisfying summer - the release of the Inference Engine Component Suite v2.0 in July was a landmark for us.  We hope that everyone has had as good a few months.
 
The major article in this issue is about a free download with source available from our website, the New Collection Wizard, usable by any of our customers.  There is also a great tip for IECS v2.0 users, for creating a case-insensitive version of the InferenceEngine.  Finally, there are a couple of news items and downloads we want you to know about.

A reminder, this newsletter and all previous ones are available for download from the web site at http://www.riversoftavg.com/articles_&_tips.htm


Contents

    Article: Using the New Collection Wizard  
    News: IECS v2.01 has been released
    News: RiverSoftAVG's IECS available on the Delphi 7 Companion CD
    Tip: Making the IECS v2.0 case-insensitive
    Download: New IECS Help files (for version 1 and 2) are available

Note: You received this newsletter because you are an owner of a RiverSoftAVG product.  If you received this newsletter by mistake or if for any reason you wish to not receive any future mailings from RiverSoftAVG, just reply to this message informing us so.  We apologize for any intrusion.


Article: Using the New Collection Wizard

For component writers, Delphi's TCollection class is a well-designed and time-saving list/collection class.  Though it is not the fastest list object, component writers descending from this class get automatic support for their list/collection object in the IDE, automatic support for assignment statements, and well designed collection-manipulation methods.  However, programming a new collection class can be tedious: you need to fill in the collection item's assign method and, even worse, override many methods in the TCollection to typecast your new collection item type.  You find yourself writing many little routines like this:
 
function TMyCollection.Add: TMyCollectionItem;
begin
     result := TMyCollectionItem(inherited Add);
end;
 
We have created a handy little wizard for Delphi to take the drudgery out of coding TCollection descendants, available from the website at www.RiverSoftAVG.com/downloads.htm  This article explains how to install and use this New Collection Wizard to quickly and safely create TCollection and TCollectionItem descendants.  Note that this wizard can be used in Standard, Professional, or Enterprise versions of Delphi.  Also, note that this article assumes some familiarity with programming components.
 
How to install the New Collection Wizard
1. Unzip the file into any directory.
2. Start Delphi.  Open the CommonWizards.dpk package
3. This package requires the PasLexD60 package (a freeware Pascal Parser/Lexer, also available from our download package).  If you are using Delphi 5, you need to remove PasLexD60 from the package and add the correct version of the PasLex package
3. Compile and Install the package
4. That's it!  You're done.  The New Collection Wizard is now available from the File->New->Other... window in the "New" tab sheet.
 
Ok, now that it is installed, let's use it.  The New Collection Wizard adds the source code it generates to the current file you are editing.  To keep this tutorial as straightforward as possible, let's create a new unit and work in that.  First, create the unit (File->New...->Unit).  Now, start the wizard by opening the New Collection Wizard in the File->New...->Other... New tab sheet.  You should see a window appear titled "New Collection Wizard".  There are 4 edit boxes/combo boxes at the top, a 3 tab page control in the middle, and Ok/Cancel buttons at the bottom.
 
The 3 tab sheets in the middle specify Options, Collection Item properties, and a Preview.  We will discuss the Options and Collection Items Properites in a moment, but the preview tab is especially useful.  This tab shows you the code that the New Collection Wizard will generate based on your inputs and options.  The easiest way to understand the different edit boxes and options of the wizard is to experiment and then check the code in the Preview pane.
 
The 4 edit boxes are where you specify the name of the collection and the collection item, what class the collection descends from, and the owner of the collection.  So, for example, if you wanted to define a TMyCollection = class(TOwnedCollection), which has TMyCollectionItem items and whose owner is TMyComponent, you would fill in TMyCollectionItem, TMyCollection, TOwnedCollection, and TMyComponent from top to bottom in each of the edit boxes.
 
The Options tab sheet in the middle of the wizard controls what source code is generated.  The first two Options specify if you want the TCollectionItem and/or the TCollection class to be generated.  The rest of the options control what parts of the TCollectionItem or TCollection class code is generated. 
 
Checking "Add Collection Reference to Owned Collection Item" generates code in the collection item to correctly reference the Owning Collection by the new TCollection type, e.g., 
property Collection: TMyCollection read GetCollection write SetCollection;  
 
 
The "Add Owner Reference to Owned Collection" check box serves the same purpose for the TCollection descendant to reference the owning component, e.g.,
function Owner: TMyComponent; reintroduce;
 
 
The "Add "Safe" Constructor" check box controls whether a new constructor should be generated for the TCollection descendant which hides the TCollectionItems created and doesn't allow it to be changed.  Both the TCollection and TOwnedCollection classes define constructors which allow you to pass in the TCollectionItemClass it should create.  By checking this box, the wizard generates a new constructor which only allows passing in the Owner to the collection, e.g.,
constructor Create(AOwner: TMyComponent);
 
When the "Add Assign Method to Collection Item" check box is checked, the wizard overrides the Assign method for the TCollectionItem and puts in code for assigning the properties of the collection item, e.g,
procedure TMyCollectionItem.Assign(Source: TPersistent);
begin
     if Source is TMyCollectionItem then
     begin
          // Copy properties here
          FName := TMyCollectionItem(Source).Name;
          FSize := TMyCollectionItem(Source).Size;
          Changed(False);
     end
     else
         inherited Assign(Source);
end;
 
 
Finally, the "Override GetDisplayName method in Collection Item" check box controls whether the wizard generates code for the collection item to retrieve a different DisplayName than the default.  If this check box is checked, the wizard generates the overridden method and puts a TODO in the code for you so that you know to put the proper code in, e.g.,
function TMyCollectionItem.GetDisplayName: String;
begin
     result := '';  {TODO: Update code for Display Name based on your properties}
     if result = '' then
        result := inherited GetDisplayName;
end;
 
 
The Collection Item Properties tab sheet allows you to enter properties for your collection item.  The wizard allows you to define the property name and type, private field, and optional read and write accessor methods.  The wizard will take care of writing all the code for you, including calling the Changed method for the SetProperty method and copying the property in the Assign method.
 
There are some limitations to the New Collection Wizard:
bulletIt doesn't allocate any fields that are classes in the TCollectionItem descendant, e.g., property Font: TFont.  You need to allocate and deallocate any object properties in the constructor and destructor for the TCollectionItem.  Be careful to also adjust the TCollectionItem.SetXXX method and the TCollectionItem.Assign method to correctly copy the property.
bulletIt doesn't set defaults for your TCollectionItem properties.  You need to set the defaults for your properties in the constructor.  To ensure Delphi doesn't stream out unnecessary information, you should also use the default keyword in the property declarations.
bulletYou need to manually override the code in the TCollection.Update and Notify protected methods to communicate with the owning object:
procedure TMyCollection.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
     inherited Notify(Item, Action);
     if Owner <> nil then
     case Action of
          cnAdded: Owner.ItemAdded(TMyCollectionItem(Item));
          cnExtracting, cnDeleting: Owner.ItemDeleted(TMyCollectionItem(Item));
     end;
end;
procedure TMyCollection.Update(Item: TCollectionItem);
begin
     inherited Update(Item);
     if Owner <> nil then
        Owner.ItemUpdated(TMyCollectionItem(Item));
end;
While there are significant limitations, this wizard is a powerful way to create collections quickly and safely.  Already, this wizard has had an impact in our code development.  The quick generation of code for the properties alone makes this wizard invaluable.  We hope you will find this wizard just as useful to you in your development.
 

News: IECS v2.01 has been released

We have just made a small, but very important, update to the IECS v2.0.  The new version, IECS v2.01, rolls in the bug fix at v2.0's release with some important new features:
bulletUpdated Code to Support the New Delphi 7!!!
bulletUpdated Help Files with Hierarchies for classes (a la Delphi)
bulletOptimized the IECS for size and speed.  The new version is 8.5% faster than the v2.0 release in general, and facts are over 10% smaller.  Plus, for large expert system files, parsing can be 15x-20x faster (if not more)!
bulletStomped some bugs, including the IECS Wizard exception
An evaluation version of the IECS v2.01 is available directly at our website at www.RiverSoftAVG.com/downloads.htm or from Borland's Code Central at http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=18413 
 

News: RiverSoftAVG's IECS available on the Delphi 7 Companion CD
 
RiverSoftAVG is proud to announce that an evaluation version of its latest flagship product, the Inference Engine Component Suite v2.0, will be shipping on Delphi 7's Companion CD.  The version that will be on the Companion CD (v2.0), can also be downloaded from Code Central at http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=18413 
 

Tip: Making the IECS v2.0 case-insensitive

 
The new IECS v2.0 makes adding and modifying parsers incredibly easy.  More methods and variables are available to tweak the behavior of the parsers.  In addition, the IECS is more "parser-aware" and will use the new parsers even in the editor dialogs.  This tip shows how to create a new parser and use it in the IECS to make the IECS syntax case-insensitive.  Note: technically, this tip does not make the IECS case insensitive.  Rather, it transform the inputs to lowercase.  So, the user can write "DefRule MyName", "DEFRULE MYNAME", or "dEfRuLe MyNaMe" and they all mean the same for the IECS, "defrule myname".

To make the IECS case-insensitive, we need to perform two functions: convert user input to one case (either Uppercase or Lowercase) and convert the internal variables and names used by the IECS to the same case.  Since the IECS functions are all already lowercase, we are going to convert the user inputs to lowercase to save effort (though it is certainly possible to change all the function names).
 
First, create a new component in Delphi.  The ancestor class will be TInferenceEngine (or TDBInferenceEngine).  For our example, we will call the new component, TCIInferenceEngine (the CI stands for case insensitive).  Delphi will create the unit for us and make the class declaration:
  TCIInferenceEngine = class(TInferenceEngine)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
  end;
 
 
To make the user input case-insensitive, it turns out that we don't need to change the parser at all.  Instead, we need to change the tokenizer to change all parsed tokens to lowercase (except for strings, we want to allow the user to specify case in strings).  We need to make a new class which descends from TIEClipsStream.  The TIEClipsStream class has a protected method, FinishToken, which is called whenever a token is parsed.  We override this method and convert the string input to lowercase (you need to add IEInterfaces to the uses clause too):
 
  TCIStream = class(TIEClipsStream)
  private
    { Private declarations }
  protected
    { Protected declarations }
    function FinishToken(AChar: Integer;
      const AString: String): IStreamToken; override;
  public
    { Public declarations }
  published
    { Published declarations }
  end;
 
{ TCIStream }
 
function TCIStream.FinishToken(AChar: Integer;
  const AString: String): IStreamToken;
begin
     if AChar <> Ord('"') then
        result := inherited FinishToken(AChar, LowerCase(AString))
     else
        result := inherited FinishToken(AChar, AString);
end;
 
Our code above checks the AChar parameter, which gives a hint about the token data type to create, to see if it is a string.  If it is not, it converts the AString input parameter to lowercase and calls the inherited FinishToken method.  Otherwise, if it is a string, we leave the AString input parameter alone and call the inherited method.
 
Voila!  We are done with the parsing and tokenization.  To make our new TInferenceEngine component USE the new tokenizer, we set the TokenizerType property.  For our purposes, we will set the TokenizerType property in the TCIInferenceEngine constructor:
constructor TCIInferenceEngine.Create(AOwner: TComponent);
begin
     inherited Create( AOwner );
     TokenizerType := TCIStream;
end;
 
 
Ok, now we have finished the user input part of our problem.  To make sure the TInferenceEngine functions and variables match the lowercase user-input, we need to set some global variables.  We will create an initialization section for the unit and set the variables (note for the following code you need to add the IEConsts and IEValue units to your uses clause):
initialization
   SDefaultSlotName := LowerCase(SDefaultSlotName);
   SInitialFactPattern := LowerCase(SInitialFactPattern);
   SInitialFact := LowerCase(SInitialFact);
   SEmptyFuzzy := LowerCase(SEmptyFuzzy);
   SLocalBinding := LowerCase(SLocalBinding);
   SPatternBinding := LowerCase(SPatternBinding);
   SRuleBinding := LowerCase(SRuleBinding);
   SNULLFact := LowerCase(SNULLFact);
   STypeSymbolKeyword := LowerCase(STypeSymbolKeyword);
   STypeAtomKeyword := LowerCase(STypeAtomKeyword);
   STypeStringKeyword := LowerCase(STypeStringKeyword);
   STypeIntegerKeyword := LowerCase(STypeIntegerKeyword);
   STypeFloatKeyword := LowerCase(STypeFloatKeyword);
   STypeLexemeKeyword := LowerCase(STypeLexemeKeyword);
   STypeNumberKeyword := LowerCase(STypeNumberKeyword);
   STypeClassKeyword := LowerCase(STypeClassKeyword);
   STypeDescriptorKeyword := LowerCase(STypeDescriptorKeyword);
   STypeObjectKeyword := LowerCase(STypeObjectKeyword);
   STypeExternalAddrKeyword := LowerCase(STypeExternalAddrKeyword);
   STypeFuzzyKeyword := LowerCase(STypeFuzzyKeyword);
   STypeFactKeyword := LowerCase(STypeFactKeyword);
   STypeFactIdKeyword := LowerCase(STypeFactIdKeyword);
   STypeVariableKeyword := LowerCase(STypeVariableKeyword);
   FCTrue.AsAtom := 'true';
   FCFalse.AsAtom := 'false';
   FCEOF.AsAtom := 'eof';
 
 
Note that, in general, this new unit cannot coexist with regular TInferenceEngine components because they use the same global variables.
 
At this point, we are done.  You can easily test this component.  Create a new application and form and add your component unit to the uses clause.  In the OnCreate event of the form, create the TCIInferenceEngine component:
procedure TForm1.FormCreate(Sender: TObject);
begin
     CIInferenceEngine1 := TCIInferenceEngine.Create(Self);
end;
 
Drop a button and a TConsoleDialog component on the form.  In the button's OnClick event, assign the CIInferenceEngine1 component to the console dialog's Engine property and execute it:
procedure TForm1.Button1Click(Sender: TObject);
begin
     ConsoleDialog1.Engine := CIInferenceEngine1;
     ConsoleDialog1.Execute;
end;
 
Compile and run the application.  Click the button and play with the new case-insensitive inference engine!
 
 
Download: New IECS Help files (for version 1 and 2) are available

The IECS help files have been updated (for v1.11 and v2.0).  The new files have "Hierarchy" pages for every class and sorted "See Also" pages.  You can download the files at:
 
v1.11
v2.01
 
Send mail to webmasterNO@SPAMRiverSoftAVG.com with questions or comments about this web site.
Copyright 2002-2010 RiverSoftAVG
Last modified: September 20, 2010