Copyright: Copyright ©1995 by Glynn M. Owen


Contents

What is it?

What's it good for?

Who do I call?

Technical Specifications

Disclaimers & cop-outs

Delphi is a trademark of Borland, International, Inc.


What it is

This help file describes and defines objects of type TAlgXpr. These objects are implemented by the code in the ALGXPR.DCU file, and available to any Delphi program which includes that unit.

Each TAlgXpr object stores a single algebraic expression in text form, along with the result determined by  evaluating that expression. The result is stored in the form of a TVarRec so that it may be either a string, an extended real number, or an integer. When a TAlgXpr object is created, it has no associated text, and its result is an integer value of 0. Text can be assigned to a TAlgXpr by either of the SetTextPChar or SetTextString methods.

When text is assigned to a TAlgXpr, code is invoked to parse the expression and reduce it to a set RPN codes consisting of a mixture of TOperator and TOperand objects. The RPN codes associated with a particular expression may be re-evaluated at any time by using the TAlgXpr.Eval method to update the result of the expression. The result will change if any of the variables involved in the expression have their values changed.

The text of an algebraic expression is stored as a character pointer. This method of storage was chosen so that expression could be longer than the 255-character limit imposed on standard Pascal strings. It should never be important, but it might be nice to know that, the size limit is 32767 characters because an integer is used as an index into the character buffer in which the expression is stored.


TAlgXpr.GetText

Function GetText: PChar

This TAlgXpr method returns the pointer to the character buffer which maintains the text of the algebraic expression. It is important to understand that the text in that buffer remains the property of the TAlgXpr, so this pointer should NEVER be disposed. It should also not be used after the text is changed, since the pointer will most likely become invalid. If you need your own copy of the text, use code like this -

Var

MyCopy: PChar;

Xpr:TAlgXpr;

...

MyCopy := StrNew( Xpr.GetText);

...


Algebraic Expressions

An algebraic expression is any text string that matches the pattern below:

Xpr	 := Atom | UnaryOp Xpr | Xpr BinaryOp Xpr

Atom	 := Variable | Literal | Function(XprLst) | (Xpr)

XprLst:= {Xpr[,XprLst]}

The := symbol means "is a". The | symbol is an exclusive OR separating a list of options of which exactly one must be chosen. The {} braces symbolize an option that may appear either 0 times or 1 time. The [] braces symbolize an option that may be used 0 times or more. The parentheses that appear in this pattern are literal characters. All other text is intended to be descriptive of some portion of an algebraic expression.

The first line reads -

An expression is an atom, or a unary operation followed by an expression, or two expressions separated from one another by a binary operation.

The second line reads -

An atom is either a variable, a literal, a function followed by its parameter list, or any expression surrounded by parentheses.

The third line reads -

An expression list is either empty, or else it consists of a comma-separated list of expressions.

Note that an expression must have some content. Empty expressions cause an exception.


Variables

Variables are names used to represent values that may be either numbers or strings. A default numeric value of 0.0 is assigned to every variable the first time that the variable is used in an expression. The ASSIGNMENT OPERATOR can be used to assign the result of any expression as the content of any variable.

NameSet: Set of char = ['a'..'z','A'..'Z','_'];


Literals

Literal values used in an expression may be numbers or strings.

Numeric literals are converted into the Delphi extended real datatype by a call to the StrToFloat function. This call can result in an EConvertError exception being raised if the format of the number is incorrect. Examples of valid numeric literals are -

1

1.0

0.0

3.14E10

Leading zeroes are required to enter purely decimal numbers. For example, .033 is NOT a valid numeric literal. To enter the intended value, use 0.033.

String literals may be enclosed by matched pairs of single or double quote marks. It is an error to open with one type of quote mark and close with the other. There is no provision for entering the terminating quote mark as any part of the string. If you want to include quoted material in the string, you must decide which type of quote to use within the string, and use the other type of quote to delimit the string. Some examples of string literals are -

"This is a string"

'This is also  string'

'My name is "Barbara"'

"Your name is 'Mudd'"


TAlgXpr.SetTextPChar

This TAlgXpr method is used to assign the contents of a null-terminated string to an instance of TAlgXpr. The contents pointed at by the TXT parameter are assumed to have been created by a call to the StrNew function of Delphi so that it can be disposed of by a call to StrDispose. For example,

X.SetTextPChar(Memo1.Lines.GetText)

is a good way to assign the contents of a memo field to the TAlgXpr instance named X.

It is important to understand that the text pointed at by the TXT parameter becomes the property of the TAlgXpr. This means that the TXT pointer must NOT be disposed of by any other part of the program. For example, to pass the contents of a temporary buffer to a TAlgXpr named X, use this syntax -

X.SetTextPChar(StrNew(Buffer))

This provides X with its own copy of the buffer contents, so that whatever happens to the buffer contents after this call will have no effect on X.


Assigning Values to Variables

The assignment operator is one of the pre-defined operations of the TAlgXpr object. It is the only way to place a value in a variable. Assignment is treated as a binary operation which requires a variable name on its left side. Any expression may appear on the right side. The value of the assignment operation is the same as the value of the expression on the right side. This permits expressions like X := Y := Z := 2 to be evaluated. This expression has a value of 2, and the side effect of storing that value in the three variables named X, Y, and Z.

Another example which illustrates this useful property of the assignment operator is the expression (X:=3.14)*2. This expression evaluates as 6.28, and has the side effect of setting the value of the variable X to 3.14. An example of the more usual way to use this operation is X := 2*3+4, which will 10 in the variable X.


Pre-defined Operations

The following binary operations are pre-defined for TAlgXpr objects -

:=	Assignment	(Numeric,String)

*	Multiplication	(Numeric)

/	Division	(Numeric)

+	Addition	(Numeric,String)

-	Subtraction	(Numeric)

^	Exponentiation	(Numeric)

%	Modulo	(Numeric)

The following unary operation is pre-defined -

-	Negation	(Numeric)

The following functions are pre-defined -

Min(ExpressionList)	(Numeric,String)

Max(ExpressionList)	(Numeric,String)

An outstanding feature of the TAlgXpr object is the fact that these pre-defined operations may be replaced by customized implementations AND more functions may be added at any time.


Replacing Pre-defined Operations

The TAlgXpr object includes


Operations

This word is used as a generic term for the unary operations, binary operations, and functions which are included in the list of those operations which are accessible in an algebraic expression. A number of pre-defined operations are written into the ALGXPR unit. New operations can be added to the list through the AddFunction method of a TAlgXpr.


TAlgXpr.AddFunction

This method is defined as -

Procedure AddFunction(const N: string; O: TFunction);

The parameter N is the name of the function, and may be any string of characters. The parameter O is an instance of a TFunction.


DataTypes
Data Types of the TAlgXpr unit

Datatypes related to operations:

TOprtnProc = Procedure(

var Rslt:TVarRec;

IX:Integer;

var Parms:array of const

);

TBinding = (

LeftBound,

RightBound

);

TOpType = (

Operand,

BinaryOp,

UnaryOp,

Functional,

Punctuation,

ParmCount,

VarPtr

);

TOp = class(TObject)

public

OpType: TOpType;

End;

TFunction = class(TObject)

public

Exec: TOprtnProc;

ParamCount: integer;

Precedence: integer;

Binding: TBinding;

OpType: TOpType;

Constructor Create(

Proc: TOprtnProc;

PCount,Prec: integer;

OType: TOpType;

Bind: TBinding

);

End;

TOperator = class(TOp)

public

Op: TFunction;

VarPtr: Pointer;

Constructor Create(O: TFunction);

End;

TProgVar = class(TObject)

Private

X: TVarRec;

Public

Constructor Create(var D; T:Byte);

Procedure	Free;

Procedure	DisposeData;

Procedure	SetValue(var D; T: byte);

Function	GetValue: TVarRec;

End;

TOperand = class(TOp)

public

V: TProgVar;

Constructor Create;

End;

TOpStack = class(TList)	{ List of operations/operands }

Public

Procedure Push(O: TOp);

Procedure Pop;

End;

TVarList = class(TStringList)

Public

Constructor	Create;

Procedure	Free;

Function	AddVar(

Name: string;

var D; T: byte

): Integer;

Procedure DeleteVar(Name: string);

Procedure SetVarValue(

var Name: string;

var D; T: byte

);

Function GetVarValue(var Name: string)

: TVarRec;

End;

TAlgXpr = class(TComponent)

Private

XprTxt:	PChar;

Public

Constructor	Create(Owner: TComponent); override;

Destructor	Destroy; override;

Procedure	SetVarTable(Tbl: TVarList);

Procedure	PutVarTable(Tbl: TVarList);

Function	GetVarTable: TVarList;

Function	OprtnIXfromPtr(P: pointer)

: Integer;

Function	OprtnFromIX(IX: Integer)

: TFunction;

Procedure	AddFunction(

const N: string;

O: TFunction

);

Function GetFunction(const N: string)

: TFunction;

End;

VarTbl: TVarList;


Type TOprtnProc

This datatype defines those procedures which may be added to the list of operations that may be used as part of an algebraic expression.


TOprtnProc

TOprtnProc = Procedure(

var Rslt:TVarRec;

IX:Integer;

var Parms:array of const

);

This type of procedure is used to implement all of the operations that can be part of an algebraic expression. See the Delphi online help for a discussion of the "TVarRec" record and the "array of const" declaration.

The parameters of each operation are passed as TVarRecs in the PARMS array. The parameters are stored in the same order they are encountered when read from left to right. For example, when the binary addition procedure is called to implement the + operator to calculate A + B, the value of A is in Parms[IX-1] and the value of B is in Parms[IX]. The RSLT parameter is used to return the result of the calculation.


TBinding

TBinding = (

LeftBound,

RightBound

);

An element from this set of values is required as a parameter when any new operation is added to the list of operations. This parameter is only needed for binary operations to determine whether their action is similar to that of addition or that of exponentiation when that same binary operation is cascaded. For example, 2+3+4 evaluates as if it were written as ((2+3)+4) because Addition is LeftBound. Exponentiation is RightBound, so that 2^3^4 evaluates as (2^(3^4)) = 2^81 instead of ((2^3)^4) = 8^4.


Operations List

The TAlgXpr unit maintains a TList of operations that are available for use in an algebraic expression. This list is only accessible through these methods of the TAlgXpr object:

OprtnIXfromPtr(P: pointer)

OprtnFromIX(IX: Integer): TFunction;

AddFunction(const N: string; O: TFunction);

GetFunction(const N: string): TFunction;

ListOperations: TStringList;

These functions allow operations to


TAlgXpr.OprtnIXfromPtr

Function OprtnIXfromPtr(P: pointer): Integer;


TOpType

TOpType = (

Operand,

BinaryOp,

UnaryOp,

Functional,

Punctuation,

ParmCount,

VarPtr

);

The elements of this set are used to classify the tokens that are extracted from each algebraic expression as it is parsed. An OPERAND is either a literal value or a variable name. A BINARYOP is a binary operation similar to addition, multiplication, subtraction, and division. A UNARYOP is a unary operation similar to numeric negation. A FUNCTIONAL is any operation that is implemented as a function name followed by a parameter list.

The Otype parameter of TFunction.Create must be either a BINARYOP, a UNARYOP, or a FUNCTIONAL.


TFunction

TFunction = class(TObject)

public

Exec: TOprtnProc;

ParamCount: integer;

Precedence: integer;

Binding: TBinding;

OpType: TOpType;

Constructor Create(

Proc: TOprtnProc;

PCount,Prec: integer;

OType: TOpType;

Bind: TBinding

);

End;

Objects of this type are stored in the Operations List. Each operation has a corresponding TFunction object which defines its characteristics.

EXEC

This parameter is a pointer to a TOprtnProc. This pointer can be changed to redefine the code used to implement any operation, including the pre-defined operations.

PARAMCOUNT

If this parameter is positive, then the operation requires exactly the number of parameters that are specified. If it is negative, then the number of parameters may be any number from 0 up to the absolute value of this parameter. The MIN and MAX functions are pre-defined with a PARAMCOUNT of -20. This means that any number of parameters may be passed to these functions from 0 through 20. The code  which implements these operations checks the actual parameter count, and raises an exception if 0 parameters are passed.

PRECEDENCE

This parameter determines the precedence of any binary operation with respect to all other binary operations.


Precedence

Binary operations must be associated with a precedence level so that algebraic expressions without parentheses are evaluated in the expected manner, rather than being evaluated strictly from left to right. For example, the expression 2+3*4 is assumed to be (2+(3*4)) = 14 and not ((2+3)*4) = 20. Binary operations with higher precedence levels are evaluated first. Parentheses can always be used to change the order of evaluation. The pre-defined binary operations have these precedence levels -

+	Addition	100

-	Subtraction	100

*	Multiplication	200

/	Division	200

%	Modulo	200

^	Exponentiation	300

:=	Assignment	1500


TOp

TOp = class(TObject)

public

OpType: TOpType;

End;

This object provides the base class for the operations and operands extracted from an algebraic expression while it's being parsed. No instance of this class is ever created. It serves only as a container for its OpType field.


TOperator

TOperator = class(TOp)

public

Op: TFunction;

VarPtr: Pointer;

Constructor Create(O: TFunction);

End;

This object defines those descendants of TOp which are operations. The OP field is used to hold a pointer to the procedure which implements the operation. The VARPTR field is only used for the Assignment operator. The constructor does nothing more than place its parameter into the OP field.


TProgVar

TProgVar = class(TObject)

Private

X: TVarRec;

Public

Constructor Create(var D; T:Byte);

Procedure	Free;

Procedure	DisposeData;

Procedure	SetValue(var D; T: byte);

Function	GetValue: TVarRec;

End;

This object defines the variables that are created the first time each variable name is encountered. When first encountered, every variable is assigned the extended real value of 0.0. The only way to change the value of variable is to use the Assignment operator.

X is a TVarRec that maintains the actual content of the variable. This record type is defined in the Delphi Help file.

The constructor for these objects takes a VAR parameter named D that must be either an extended real number, an integer, or a string. Which of these datatypes is actually passed in D must be specified by the BYTE parameter named T. Acceptable values for T are one of these three system constants: vtExtended, vtInteger, vtString.

The FREE method destroys the TProgVar object after releasing any memory that may have been allocated to the value in X.

The DISPOSEDATA method release any memory allocated to the value in X.

The SETVALUE method calls DISPOSEDATA before it sets the value of X according to the contents of D and T parameters, which are the same as those passed to the constructor.

The GETVALUE function returns a copy of X.


TOperand

TOperand = class(TOp)

public

V: TProgVar;

Constructor Create;

End;

This object defines those descendants of TOp which define the operands that are extracted from an algebraic expression as it is parsed. An operand is either a variable name or a literal value.


TVarList

TVarList = class(TStringList)

Public

Procedure	Free;

Function	AddVar(

Name: string;

var D; T: byte

): Integer;

Procedure DeleteVar(Name: string);

Procedure SetVarValue(

var Name: string;

var D; T: byte

);

Function GetVarValue(var Name: string)

: TVarRec;

End;

This object is a sorted list of strings. Each string is the name of a variable that has been encountered during the parsing phase of the evaluation of some algebraic expression. The TAlgXpr unit creates one of these lists when it initializes. That list is stored as the exported variable named VarTbl. Each time a variable name is encountered for the first time, it is stored in this list. Thereafter, any reference to that name will use that entry in this list.

The CREATE method is over-ridden just to insure that the table created is sorted.

The FREE method is over-ridden to insure that the associated TProgVar objects are freed along with the list.

The ADDVAR method creates a named variable of the type specified in the T parameter, which should be one of the values vtInteger, vtString, or vtExtended. The parameter named D must be a variable corresponding to the type that is passed in T.

The GETVARVALUE function returns a copy of the TVarRec that is associated with the TProgVar which holds the value of the variable.


RPN codes

RPN stands for Reverse Polish Notation, which is an alternative method for writing and evaluating algebraic expressions. For example, this algebraic expression

sin((1+2)*3+4)

would be written in RPN as

1 2 + 3 * 4 + sin

The advantage to RPN expressions is that they do not require parentheses. An RPN expression is evaluated by reading it from left to write, stacking up each operand, and then submitting those operands to each operation as it is encountered. The result from each operation replaces the operands that it uses. A trace of the stack for the example above would look like this -

1    3    9    13   .22495

2    3    4    sin

+    *    +


Parse

When the text form of an algebraic expression is submitted to TAlgXpr object, that text is examined on a character-by-character basis to break it down into tokens. A token can be any variable name, any literal string or number, any operation, or any of the recognized punctuation marks.


Punctuation

There are only a few punctuation marks recognized as part of an algebraic expression. Those are the left and right parentheses, the comma, and both the single and double quote marks. Any other punctuation mark will be interpreted as an operator, and will cause an exception if it is not in the Operations List.

The comma is used only to separate one expression from the next in a comma-separated list of parameters to a functional.


Functional

Many operations, MIN and MAX in particular, are implemented as functionals, meaning that when they appear in an algebraic expression, they consist of a function name followed by a comma-separated list of parameters which is enclosed by a matched pair of parentheses. For example, MIN(3*2, 2*4, 8). This example shows a comma-separated parameter list of 3 parameters.

Most functions require a specific number of parameters, usually 0, 1 or 2. For example, the SIN function requires only a single parameter. If you pass it any other number of parameters, an exception will be raised.

Other functions will take a variable number of parameters. The MIN and MAX functions will take any number of parameters from 1 through 20.


TAlgXpr.SetTextString

This TAlgXpr method can be used to set the text of a TAlgXpr to be the contents of any Pascal string. If you are not familiar with the distinction between an null-terminated strings and a Pascal string, then you should use this function to set the text of a TAlgXpr object.


TAlgXpr

TAlgXpr = class(TComponent)

Public

Destructor	Destroy; override;

Procedure	Eval;

Procedure	SetTextPChar(Txt: PChar);

Procedure	SetTextString(const Txt: String);

Function	GetText: PChar;

Function	XprResult: TVarRec;

Procedure	SetVarTable(Tbl: TVarList);

Function	GetVarTable: TVarList;

Function	OprtnIXfromPtr(P: pointer): Integer;

Function	OprtnFromIX(IX: Integer): TFunction;

Procedure	AddFunction(const N: string; O: TFunction);

Function	GetFunction(const N: string) : TFunction;

End;


TAlgXpr.Destroy

Each instance of a TAlgXpr is associated with a number of private fields that must be created and destroyed. This method performs those functions before calling the inherited destructor. It can be over-ridden if more functionality is required.


TAlgXpr.Eval

This method of every TAlgXpr will re-evaluate the expression so that its result will be in accord with the current state of any variables that the expression uses. If an expression has no variables, this method does essentially nothing.


TAlgXpr.XprResult

Function	XprResult: TVarRec;

This function of a TAlgXpr returns a copy of the TVarRec record used to hold the very last result generated by the TAlgXpr.Eval method. This result is also updated by either the SetTextPChar or SetTextString methods.


TAlgXpr.SetVarTable

Procedure SetVarTable(Tbl: TVarList);

This method of a TAlgXpr can be used to change the currently active variable table. A default instance of this type is created by TAlgXpr's initialization code, and exported under the name VarTbl. That name is used in all the places that a variable list is required. This procedure will let you change the table referred to by that name.

Before you change the currently active variable table, you need to understand that this procedure does nothing to dispose of the table that is active when this method is called. If you don't use the GetVarTable method to free that table or to store a copy of it, then that table will be orphaned until the session ends and Delphi frees it.


TAlgXpr.GetVarTable

Function GetVarTable: TVarList;

This function of a TAlgXpr returns the currently active variable list. An example of its use is this -

X.GetVarTable.GetVarValue('TIME')

This example returns a TVarRec that is the value of the variable named TIME.

Another use for this method is to store a copy of the currently active variable table before replacing that table with another. It can also be used to free the currently active variable table, but if you do that you must use the SetVarTable method to install another valid table before using any TAlgXpr again again.


TAlgXpr.GetFunction

Function GetFunction(const N: string) : TFunction;

This function will return the TFunction object associated with the name passed in the N parameter. If the name is not found, no exception is generated. Instead, the value NIL is returned.

The following example uses this function to locate the entry in the operations list associated with the operation named MIN -

Var	Op: TOperation;

Op := GetIXFromPtr(GetFunction('MIN'))


TAlgXpr.OprtnFromIX

Function OprtnFromIX(IX: Integer): TFunction;

This function of returns the TFunction stored at the index passed in the IX parameter. The only way to retrieve such an index is by using GetFunction to get a pointer to the operation, and then GetIXFromPtr to retrieve the actual index. For example,

Var	Op: TFunction;

Op := OprtnFromIX(GetIXFromPtr(GetFunction('MIN')))

This example will store a pointer to the code which implements the MIN function in the variable named Op.


What's it good for?

Because any kind of operation can be added to the operations list, the TAlgXpr object can be the basis of any kind of general or specialized calculator.

Because all objects of type TAlgXpr use the same variable table, the value of an expression can be modified by modifying the contents of the variables that it uses. This makes the TAlgXpr an excellent tool for demonstrating basic Algebra.

Because TAlgXpr objects are built on a text parser, the contents of that text can be entered by the user as a macro for other program features. Specifically, TAlgXpr objects can be used to create macros in custom database applications, so that complicated QBE is not required.


Who do I call?

This code is written and supported by Glynn Owen. Support is available only to registered users. While I have made every effort to insure the code is solid, any use that you may make of it is entirely at your own risk.

Compuserve account number:	71073,2552.

E-mail:	71073.2552@compuserve.com.

Phone Number: 	(909) 654 0885.


Disclaimers & cop-outs

This unit is provided on an as-is basis. The user agrees to accept all responsibility for its use. This preliminary release is designed to operate only when Delphi is running. It won't work in a stand-alone situation. Registered users will receive the fully-functional version via E-mail over Compuserve.