Here are instructions for setting up a project to use the TALGXPR datatype. The main form of the program is assumed to be an instance of TFORM1 named FORM1. TFORM1 is assumed to have a field defined as EXPRESSION: TALGXPR that may be either public or private. (1) Make sure that ALGXPR appears in the USES clause of the FORM1 unit. (2) Use the following code to initialize the FORM1.EXPRESSION instance of TALGXPR. Any other instance of TALGXPR must be initialized by similar code. { ******* START CODE ********** } TFORM1.FormCreate(Sender: TObject) Begin {... Whatever } EXPRESSION := TALGXPR.Create; {... Whatever } End; { ******* END CODE ********** } (3) In the TFORM1.CLOSE method, this code should appear - TFORM1.FormCreate(Sender: TObject) { ******* START CODE ********** } Begin {... Whatever } EXPRESSION.Free {... Whatever } End; { ******* END CODE ********** } At this point, any code in the project with access to the FORM1.EXPRESSION field can use code similar to the following to store the text form of an algebraic expression: (4) EXPRESSION.SetTextString(AnyPascalString) Almost any expression you can think of can be handled as a Pascal String by using method (4). In the event that you need to pass expression longer than 255 characters (or if you just feel like it), t is possible to use a PCHAR as the source of the text form of an algebraic expression by using code similar to this: (5) EXPRESSION.SetTextPChar(AnyPChar) The memory allocated to the pointer passed to the SetTextPChar method of any TALGXPR instance becomes the property of that TALGXPR instance. In other words, you don't have to dispose of that buffer in your own code. The TALGXPR will do that for you. You DO have to worry if you dispose of the buffer yourself, since that causes an error when the TALGXPR disposes of it a second time. It is also important to understand that the Delphi StrDispose procedure is used to dispose of this allocated memory, and therefore the PChar should be allocated by a call to Delhpi's StrNew procedure, and NOT the GetMem procedure. The SetTextPChar procedure is convenient for passing expressions that have been entered into a Delphi TMEMO object. Here is an example of how that can be done for a TMEMO named Memo1: (6) EXPRESSION.SetTextPChar(Memo1.Lines.GetText) When you pass the text form of an algebraic expression to an instance of TALGXPR, an algebraic parser reads the text, and uses it to create tokens for an RPN version of that expression. The expression is stored in both its text form and in its token form. There are a host of things that can go wrong when you pass the text form of an algebraic expression to an instance of TALGXPR. It's a case of no news is good news. The algebraic parser declares an exception if it finds anything wrong. If you don't see any such exception then the expression is valid. If an exception occurs then you only need to edit the text and try again. There is a side-effect to such exceptions. Any previous content (both text and RPN) is lost if such an exception occurs. When either (4) or (5) is used to pass a valid expression, the RPN codes of that expression are evaluated by an internal call to the TALGXPR.Eval procedure. The results of the evaluation are stored in a private field of the TALGXPR instance as a TVarRec (see Delphi Help). A copy of that TVarRec is returned by each call to the TALGXPR.XprResult function. It is important to understand that TALGXPR.XprResult does NOT recalculate the expression. It just returns a copy of the last result of any call to the TALGXPR.Eval procedure. This fact can be used to re-calculate any expression which uses variables if it is known (or even suspected) that the contents of those variables have changed. The operations and functions recognized by the algebraic parser of every TALGXPR instance are stored in TStringList that is private to the ALGXPR unit. The contents of this list are set up during the initialization of the ALGXPR unit, and destroyed by that unit's exit procedure. The list of operations cannot be directly addressed outside of the ALGXPR unit, but there is a way to add new elements to this list. This makes it possible for your own units to define new functions, binary operators, and unary operators that can be recognized by the algebraic parser. This is the procedure which makes it possible to extend the functionality of the algebraic parser: (7) TALGXPR.AddFunction(const Name: string; Operation: TFunction) The NAME parameter of this function is simply the name that will be used in any algebraic expression. This name must ALWAYS be UPPERCASE. For example, 'COS' is the name the function which implements the cosine function implemented in this demo version. The OPERATION parameter is a reference to an object of this type: TFunction = class(TObject) public Exec: TOprtnProc; ParamCount: integer; { >= 0 means this many, < 0 means 0 up to this many } Precedence: integer; Binding: TBinding; OpType: TOpType; Constructor Create( Proc: TOprtnProc; PCount,Prec: integer; OType: TOpType; Bind: TBinding ); End; The other types mentioned in the TFUNCTION definition are as follows: TBinding = ( LeftBound, RightBound ); TOpType = ( Operand, BinaryOp, UnaryOp, Functional, Punctuation, ParmCount, VarPtr ); TOprtnProc = Procedure( Var Rslt:TVarRec; IX:Integer; var Parms:array of const ); An in-depth discussion of these types can be found in the on-line help. It is the TOPRTNPOC type that is the key to adding new functionality to TALGXPR instances. Here is the definition of the COS function as implemented in this demo version: EXPRESSION.AddObject('COS',TFunction.Create(Cosine,1,2000,Functional,LeftBound)); {********************** BEGIN CODE *****************************************************} Procedure Cosine(var Rslt:TVarRec; IX:Integer; var Parms:array of const); far; Begin Case Parms[IX-1].vType of vtExtended: begin New(Rslt.VExtended); Rslt.VExtended^ := cos(Parms[IX-1].VExtended^); Rslt.VType := vtExtended End; Else AlgXprErr(ErrInvalidOprnd); End { case } End; {********************** END CODE *****************************************************} As you can see, the header of the procedure which defines the COS function conforms to the procedural type for TOPRTNPROC. Here is the code which adds the COS function to the list of functions available to the algebraic parser: EXPRESSION.AddFunction( 'COS', TFunction.Create(Cosine,1,2000,Functional,LeftBound) ); Notice that the word 'COS' is UPPERCASE. This is important, because the algebraic parser uppercases what it reads before interpreting it. Consequently, case is only important when a name is first installed (as above) and never when it's part of the text in an algebraic exxpress. Here is the definition of the * operator as implemented in this demo version: {********************** BEGIN CODE *************************************************} Procedure Multiplication(var Rslt:TVarRec; IX:Integer; var Parms:array of const); far; Begin If Parms[IX].vType <> Parms[IX-1].vType then AlgXprErr(ErrParmMatch); Case Parms[IX].vType of vtInteger: MultiplyInts(Parms[IX-1].Vinteger,Parms[IX].Vinteger,Rslt); vtExtended: MultiplyReals(Parms[IX-1].VExtended,Parms[IX].VExtended, Rslt); Else AlgXprErr(ErrInvalidOprnd); End { case } End; {********************** END CODE *************************************************} The installation code for this operator is as follows: EXPRESSION.AddFunction( '*', TFunction.Create(Multiplication,2,200,BinaryOp,LeftBound) ); The PRECEDENCE field of a TFunction is used to enforce the standard order of operations for the pre-defined binary operations of addition, subtraction, multiplication, division, modulus, and exponentiation. The precise values assigned to this field for those operations are: + - 100 * / % 200 ^ 300 For functions which you choose to define, set the PRECEDENCE field to 2000. If you create your own binary operators, then you can set the PRECEDENCE to whatever seems to work for you. I know that the binary comparison operators such as LE, GT, and EQ are sometimes given higher precedence than addition, but less precedence than multiplication. Given that FALSE is 0 and TRUE is 1, this will cause the expression 5 LT 1+2*3 to evaluate as (5 LT (1+2))*3 = 0. Sometimes, the PRECEDENCE of the comparison operators is lower than that of addition, so that 5 LT 1+2*3 evaluates as (((5 LT 1)+2)*3) = 6. Again, if the PRECEDENCE of LT is higher than that of multiplication, 5 LT 1+2*3 will become (5 LT ((1+2)*3)) = 0. The comparison operators are NOT PART of this demo version. Functions such as the COS function require exactly 1 parameter. In the example above, you can see that the PARAMCOUNT field of the TFunction created to maintain the COS function is set to 1. Any non-negative number (including 0) can be installed in this field to indicate the exact number of parameters required by the function. Under these circumstances, the algebraic parser will count the number of parameters passed to any use of that function, and report an error if the number of parameters is incorrect. On the other hand, it may be that you need a function which takes an arbitrary (but small) number of functions. The MIN function included in this demo version is a good example: {************************* START CODE ******************************************} Procedure Min(var Rslt:TVarRec; IX:Integer; var Parms:array of const); far; Var PCount: integer; Begin PCount := Parms[IX].vInteger; If (PCount > 0) Then case Parms[IX-1].vType of vtInteger: MinInts(Rslt, IX, PCount, Parms); vtExtended: MinReals(Rslt, IX, PCount, Parms); Else AlgXprErr(ErrInvalidOprnd); End {Case} Else AlgXprErr(ErrNoParms); End; {************************* END CODE ******************************************} This function is added to the list of operations by code similar to the following: EXPRESSION.AddFunction( 'MIN', TFunction.Create(Min,-20,2000,Functional,LeftBound) ); Notice that the PARAMCOUNT of the TFUNCTION object is -20. The algebraic parser interprets this to mean that any number of parameters from 0 through 20 may be passed to this function. Here is the code that implements the MIN function: {***************************************************************************} Procedure Min(var Rslt:TVarRec; IX:Integer; var Parms:array of const); far; Var PCount: integer; Begin PCount := Parms[IX].vInteger; If (PCount > 0) Then case Parms[IX-1].vType of vtInteger: MinInts(Rslt, IX, PCount, Parms); vtExtended: MinReals(Rslt, IX, PCount, Parms); Else AlgXprErr(ErrInvalidOprnd); End {Case} Else AlgXprErr(ErrNoParms); End; {***************************************************************************} Note that this code test for the possibility that 0 parameters have been passed. For this function, that is an error, but that error can't be tested until the function executes.