lazarus-ccr/components/fpspreadsheet/fpsexprparser.pas
wp_xxyyzz c87afdcdec fpspreadsheet: Redo handling for formulas:
- Allow processing of string formulas (conversion to/from rpn formulas, calculation). 
- Drop cell ContentType cctRPNFormula. 
- Drop field RPNFormulaValue of TCell record. 
- Remove all fekXXXX declarations for sheet functions. Function is specified by name now.
- Complete registration mechanism for user-defined formulas.
Adapt all demos
Test cases working
This commit does not yet support: shared formulas, formulas in ods.


git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3506 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2014-08-30 18:03:22 +00:00

5508 lines
159 KiB
ObjectPascal

{
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.
--------------------------------------------------------------------------------
Modified for integration into fpspreadsheet by Werner Pamler:
- Original file name: fpexprpars.pp
- Rename identifiers to avoid naming conflicts with the original
- TsExpressionParser and TsBuiltinExpressionManager are not components
any more
- TsExpressionParser is created with the worksheet as a parameter.
- add new TExprNode classes:
- TsCellExprNode for references to cells
- TsCellRangeExprNode for references to cell ranges
- TsPercentExprNode and token "%" to handle Excel's percent operation
- TsParenthesisExprNode to handle the parenthesis token in RPN formulas
- TsConcatExprNode and token "&" to handle string concatenation
- TsUPlusExprNode for unary plus symbol
- remove and modifiy built-in function such that the parser is compatible
with Excel syntax (and Open/LibreOffice - which is the same).
- use double quotes for strings (instead of single quotes)
- add boolean constants "TRUE" and "FALSE".
- add property RPNFormula to interface the parser to RPN formulas of xls files.
- accept funtions with zero parameters
******************************************************************************}
// To do:
// Remove exceptions, use error message strings instead
// Cell reference not working (--> formula CELL!)
// Missing arguments
// Keep spaces in formula
{$mode objfpc}
{$h+}
unit fpsExprParser;
interface
uses
Classes, SysUtils, contnrs, fpspreadsheet;
type
{ Tokens }
(* { Basic operands }
fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger,
fekString, fekBool, fekErr, fekMissingArg,
{ Basic operations }
fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus,
fekConcat, // string concatenation
fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual,
fekParen,
*)
TsTokenType = (
ttCell, ttCellRange, ttNumber, ttString, ttIdentifier,
ttPlus, ttMinus, ttMul, ttDiv, ttConcat, ttPercent, ttPower, ttLeft, ttRight,
ttLessThan, ttLargerThan, ttEqual, ttNotEqual, ttLessThanEqual, ttLargerThanEqual,
ttComma, ttTrue, ttFalse, ttEOF
);
TsExprFloat = Double;
TsExprFloatArray = array of TsExprFloat;
const
ttDelimiters = [
ttPlus, ttMinus, ttMul, ttDiv, ttLeft, ttRight, ttLessThan, ttLargerThan,
ttEqual, ttNotEqual, ttLessThanEqual, ttLargerThanEqual
];
ttComparisons = [
ttLargerThan, ttLessThan, ttLargerThanEqual, ttLessThanEqual, ttEqual, ttNotEqual
];
type
TsExpressionParser = class;
TsBuiltInExpressionManager = class;
TsResultType = (rtEmpty, rtBoolean, rtInteger, rtFloat, rtDateTime, rtString,
rtCell, rtCellRange, rtError, rtAny);
TsResultTypes = set of TsResultType;
TsExpressionResult = record
Worksheet : TsWorksheet;
ResString : String;
case ResultType : TsResultType of
rtEmpty : ();
rtError : (ResError : TsErrorValue);
rtBoolean : (ResBoolean : Boolean);
rtInteger : (ResInteger : Int64);
rtFloat : (ResFloat : TsExprFloat);
rtDateTime : (ResDateTime : TDatetime);
rtCell : (ResRow, ResCol : Cardinal);
rtCellRange : (ResCellRange : TsCellRange);
rtString : ();
end;
PsExpressionResult = ^TsExpressionResult;
TsExprParameterArray = array of TsExpressionResult;
{ TsExprNode }
TsExprNode = class(TObject)
protected
procedure CheckNodeType(ANode: TsExprNode; Allowed: TsResultTypes);
// A procedure with var saves an implicit try/finally in each node
// A marked difference in execution speed.
procedure GetNodeValue(var Result: TsExpressionResult); virtual; abstract;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; virtual; abstract;
function AsString: string; virtual; abstract;
procedure Check; virtual; abstract;
function NodeType: TsResultType; virtual; abstract;
function NodeValue: TsExpressionResult;
end;
TsExprArgumentArray = array of TsExprNode;
{ TsBinaryOperationExprNode }
TsBinaryOperationExprNode = class(TsExprNode)
private
FLeft: TsExprNode;
FRight: TsExprNode;
protected
procedure CheckSameNodeTypes; virtual;
public
constructor Create(ALeft, ARight: TsExprNode);
destructor Destroy; override;
procedure Check; override;
property Left: TsExprNode read FLeft;
property Right: TsExprNode read FRight;
end;
TsBinaryOperationExprNodeClass = class of TsBinaryOperationExprNode;
{ TsBooleanOperationExprNode }
TsBooleanOperationExprNode = class(TsBinaryOperationExprNode)
public
procedure Check; override;
function NodeType: TsResultType; override;
end;
{ TsBooleanResultExprNode }
TsBooleanResultExprNode = class(TsBinaryOperationExprNode)
protected
procedure CheckSameNodeTypes; override;
public
procedure Check; override;
function NodeType: TsResultType; override;
end;
TsBooleanResultExprNodeClass = class of TsBooleanResultExprNode;
{ TsEqualExprNode }
TsEqualExprNode = class(TsBooleanResultExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsNotEqualExprNode }
TsNotEqualExprNode = class(TsEqualExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsOrderingExprNode }
TsOrderingExprNode = class(TsBooleanResultExprNode)
protected
procedure CheckSameNodeTypes; override;
public
procedure Check; override;
end;
{ TsLessExprNode }
TsLessExprNode = class(TsOrderingExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsGreaterExprNode }
TsGreaterExprNode = class(TsOrderingExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsLessEqualExprNode }
TsLessEqualExprNode = class(TsGreaterExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsGreaterEqualExprNode }
TsGreaterEqualExprNode = class(TsLessExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsConcatExprNode }
TsConcatExprNode = class(TsBinaryOperationExprNode)
protected
procedure CheckSameNodeTypes; override;
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string ; override;
procedure Check; override;
function NodeType: TsResultType; override;
end;
{ TsMathOperationExprNode }
TsMathOperationExprNode = class(TsBinaryOperationExprNode)
protected
procedure CheckSameNodeTypes; override;
public
procedure Check; override;
function NodeType: TsResultType; override;
end;
{ TsAddExprNode }
TsAddExprNode = class(TsMathOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string ; override;
end;
{ TsSubtractExprNode }
TsSubtractExprNode = class(TsMathOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string ; override;
end;
{ TsMultiplyExprNode }
TsMultiplyExprNode = class(TsMathOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string ; override;
end;
{ TsDivideExprNode }
TsDivideExprNode = class(TsMathOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string ; override;
function NodeType: TsResultType; override;
end;
{ TsPowerExprNode }
TsPowerExprNode = class(TsMathOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string ; override;
function NodeType: TsResultType; override;
end;
{ TsUnaryOperationExprNode }
TsUnaryOperationExprNode = class(TsExprNode)
private
FOperand: TsExprNode;
protected
procedure Check; override;
public
constructor Create(AOperand: TsExprNode);
destructor Destroy; override;
property Operand: TsExprNode read FOperand;
end;
{ TsConvertExprNode }
TsConvertExprNode = class(TsUnaryOperationExprNode)
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
end;
{ TsNotExprNode }
TsNotExprNode = class(TsUnaryOperationExprNode)
protected
procedure Check; override;
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
function NodeType: TsResultType; override;
end;
{ TsConvertToIntExprNode }
TsConvertToIntExprNode = class(TsConvertExprNode)
public
procedure Check; override;
end;
{ TsIntToFloatExprNode }
TsIntToFloatExprNode = class(TsConvertToIntExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function NodeType: TsResultType; override;
end;
{ TsIntToDateTimeExprNode }
TsIntToDateTimeExprNode = class(TsConvertToIntExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function NodeType: TsResultType; override;
end;
{ TsFloatToDateTimeExprNode }
TsFloatToDateTimeExprNode = class(TsConvertExprNode)
protected
procedure Check; override;
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function NodeType: TsResultType; override;
end;
{ TsUPlusExprNode }
TsUPlusExprNode = class(TsUnaryOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
procedure Check; override;
function NodeType: TsResultType; override;
end;
{ TsUMinusExprNode }
TsUMinusExprNode = class(TsUnaryOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
procedure Check; override;
function NodeType: TsResultType; override;
end;
{ TsPercentExprNode }
TsPercentExprNode = class(TsUnaryOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
procedure Check; override;
function NodeType: TsResultType; override;
end;
{ TsParenthesisExprNode }
TsParenthesisExprNode = class(TsUnaryOperationExprNode)
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
function NodeType: TsResultType; override;
end;
{ TsConstExprNode }
TsConstExprNode = class(TsExprNode)
private
FValue: TsExpressionResult;
protected
procedure Check; override;
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
constructor CreateString(AValue: String);
constructor CreateInteger(AValue: Int64);
constructor CreateDateTime(AValue: TDateTime);
constructor CreateFloat(AValue: TsExprFloat);
constructor CreateBoolean(AValue: Boolean);
constructor CreateError(AValue: TsErrorValue);
function AsString: string; override;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function NodeType : TsResultType; override;
// For inspection
property ConstValue: TsExpressionResult read FValue;
end;
TsExprIdentifierType = (itVariable, itFunctionCallBack, itFunctionHandler);
TsExprFunctionCallBack = procedure (var Result: TsExpressionResult;
const Args: TsExprParameterArray);
TsExprFunctionEvent = procedure (var Result: TsExpressionResult;
const Args: TsExprParameterArray) of object;
{ TsExprIdentifierDef }
TsExprIdentifierDef = class(TCollectionItem)
private
FStringValue: String;
FValue: TsExpressionResult;
FArgumentTypes: String;
FIDType: TsExprIdentifierType;
FName: ShortString;
FExcelCode: Integer;
FVariableArgumentCount: Boolean;
FOnGetValue: TsExprFunctionEvent;
FOnGetValueCB: TsExprFunctionCallBack;
function GetAsBoolean: Boolean;
function GetAsDateTime: TDateTime;
function GetAsFloat: TsExprFloat;
function GetAsInteger: Int64;
function GetAsString: String;
function GetResultType: TsResultType;
function GetValue: String;
procedure SetArgumentTypes(const AValue: String);
procedure SetAsBoolean(const AValue: Boolean);
procedure SetAsDateTime(const AValue: TDateTime);
procedure SetAsFloat(const AValue: TsExprFloat);
procedure SetAsInteger(const AValue: Int64);
procedure SetAsString(const AValue: String);
procedure SetName(const AValue: ShortString);
procedure SetResultType(const AValue: TsResultType);
procedure SetValue(const AValue: String);
protected
procedure CheckResultType(const AType: TsResultType);
procedure CheckVariable;
public
function ArgumentCount: Integer;
procedure Assign(Source: TPersistent); override;
property AsFloat: TsExprFloat 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;
function HasFixedArgumentCount: Boolean;
function IsOptionalArgument(AIndex: Integer): Boolean;
property OnGetFunctionValueCallBack: TsExprFunctionCallBack read FOnGetValueCB write FOnGetValueCB;
published
property IdentifierType: TsExprIdentifierType 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: TsResultType read GetResultType write SetResultType;
property ExcelCode: Integer read FExcelCode write FExcelCode;
property VariableArgumentCount: Boolean read FVariableArgumentCount write FVariableArgumentCount;
property OnGetFunctionValue: TsExprFunctionEvent read FOnGetValue write FOnGetValue;
end;
TsBuiltInExprCategory = (bcMath, bcStatistics, bcStrings, bcLogical, bcDateTime,
bcLookup, bcInfo, bcUser);
TsBuiltInExprCategories = set of TsBuiltInExprCategory;
{ TsBuiltInExprIdentifierDef }
TsBuiltInExprIdentifierDef = class(TsExprIdentifierDef)
private
FCategory: TsBuiltInExprCategory;
public
procedure Assign(Source: TPersistent); override;
published
property Category: TsBuiltInExprCategory read FCategory write FCategory;
end;
{ TsExprIdentifierDefs }
TsExprIdentifierDefs = class(TCollection)
private
FParser: TsExpressionParser;
function GetI(AIndex: Integer): TsExprIdentifierDef;
procedure SetI(AIndex: Integer; const AValue: TsExprIdentifierDef);
protected
procedure Update(Item: TCollectionItem); override;
property Parser: TsExpressionParser read FParser;
public
function FindIdentifier(const AName: ShortString): TsExprIdentifierDef;
function IdentifierByExcelCode(const AExcelCode: Integer): TsExprIdentifierDef;
function IdentifierByName(const AName: ShortString): TsExprIdentifierDef;
function IndexOfIdentifier(const AName: ShortString): Integer; overload;
function IndexOfIdentifier(const AExcelCode: Integer): Integer; overload;
function AddVariable(const AName: ShortString; AResultType: TsResultType;
AValue: String): TsExprIdentifierDef;
function AddBooleanVariable(const AName: ShortString;
AValue: Boolean): TsExprIdentifierDef;
function AddIntegerVariable(const AName: ShortString;
AValue: Integer): TsExprIdentifierDef;
function AddFloatVariable(const AName: ShortString;
AValue: TsExprFloat): TsExprIdentifierDef;
function AddStringVariable(const AName: ShortString;
AValue: String): TsExprIdentifierDef;
function AddDateTimeVariable(const AName: ShortString;
AValue: TDateTime): TsExprIdentifierDef;
function AddFunction(const AName: ShortString; const AResultType: Char;
const AParamTypes: String; const AExcelCode: Integer;
ACallBack: TsExprFunctionCallBack): TsExprIdentifierDef;
function AddFunction(const AName: ShortString; const AResultType: Char;
const AParamTypes: String; const AExcelCode: Integer;
ACallBack: TsExprFunctionEvent): TsExprIdentifierDef;
property Identifiers[AIndex: Integer]: TsExprIdentifierDef read GetI write SetI; default;
end;
{ TsIdentifierExprNode }
TsIdentifierExprNode = class(TsExprNode)
private
FID: TsExprIdentifierDef;
PResult: PsExpressionResult;
FResultType: TsResultType;
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
constructor CreateIdentifier(AID: TsExprIdentifierDef);
function NodeType: TsResultType; override;
property Identifier: TsExprIdentifierDef read FID;
end;
{ TsVariableExprNode }
TsVariableExprNode = class(TsIdentifierExprNode)
procedure Check; override;
function AsString: string; override;
Function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
end;
{ TsFunctionExprNode }
TsFunctionExprNode = class(TsIdentifierExprNode)
private
FArgumentNodes: TsExprArgumentArray;
FargumentParams: TsExprParameterArray;
protected
procedure CalcParams;
public
constructor CreateFunction(AID: TsExprIdentifierDef;
const Args: TsExprArgumentArray); virtual;
destructor Destroy; override;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
procedure Check; override;
property ArgumentNodes: TsExprArgumentArray read FArgumentNodes;
property ArgumentParams: TsExprParameterArray read FArgumentParams;
end;
{ TsFunctionCallBackExprNode }
TsFunctionCallBackExprNode = class(TsFunctionExprNode)
private
FCallBack: TsExprFunctionCallBack;
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
constructor CreateFunction(AID: TsExprIdentifierDef;
const Args: TsExprArgumentArray); override;
property CallBack: TsExprFunctionCallBack read FCallBack;
end;
{ TFPFunctionEventHandlerExprNode }
TFPFunctionEventHandlerExprNode = class(TsFunctionExprNode)
private
FCallBack: TsExprFunctionEvent;
protected
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
constructor CreateFunction(AID: TsExprIdentifierDef;
const Args: TsExprArgumentArray); override;
property CallBack: TsExprFunctionEvent read FCallBack;
end;
{ TsCellExprNode }
TsCellExprNode = class(TsExprNode)
private
FWorksheet: TsWorksheet;
FRow, FCol: Cardinal;
FFlags: TsRelFlags;
FCell: PCell;
FIsRef: Boolean;
protected
procedure Check; override;
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
constructor Create(AWorksheet: TsWorksheet; ACellString: String); overload;
constructor Create(AWorksheet: TsWorksheet; ARow, ACol: Cardinal;
AFlags: TsRelFlags); overload;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
function NodeType: TsResultType; override;
property Worksheet: TsWorksheet read FWorksheet;
end;
{ TsCellRangeExprNode }
TsCellRangeExprNode = class(TsExprNode)
private
FWorksheet: TsWorksheet;
FRow1, FRow2: Cardinal;
FCol1, FCol2: Cardinal;
FFlags: TsRelFlags;
protected
procedure Check; override;
procedure GetNodeValue(var Result: TsExpressionResult); override;
public
constructor Create(AWorksheet: TsWorksheet; ACellRangeString: String); overload;
constructor Create(AWorksheet: TsWorksheet; ARow1,ACol1, ARow2,ACol2: Cardinal;
AFlags: TsRelFlags); overload;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: String; override;
function NodeType: TsResultType; override;
property Worksheet: TsWorksheet read FWorksheet;
end;
{ TsExpressionScanner }
TsExpressionScanner = class(TObject)
FSource : String;
LSource,
FPos: Integer;
FChar: PChar;
FToken: String;
FTokenType: TsTokenType;
private
function GetCurrentChar: Char;
procedure ScanError(Msg: String);
protected
procedure SetSource(const AValue: String); virtual;
function DoIdentifier: TsTokenType;
function DoNumber: TsTokenType;
function DoDelimiter: TsTokenType;
function DoString: TsTokenType;
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: TsTokenType;
property Token: String read FToken;
property TokenType: TsTokenType read FTokenType;
property Source: String read FSource write SetSource;
property Pos: Integer read FPos;
property CurrentChar: Char read GetCurrentChar;
end;
EExprScanner = class(Exception);
{ TsExpressionParser }
TsExpressionParser = class
private
FBuiltIns: TsBuiltInExprCategories;
FExpression: String;
FScanner: TsExpressionScanner;
FExprNode: TsExprNode;
FIdentifiers: TsExprIdentifierDefs;
FHashList: TFPHashObjectlist;
FDirty: Boolean;
FWorksheet: TsWorksheet;
procedure CheckEOF;
procedure CheckNodes(var ALeft, ARight: TsExprNode);
function ConvertNode(Todo: TsExprNode; ToType: TsResultType): TsExprNode;
function GetAsBoolean: Boolean;
function GetAsDateTime: TDateTime;
function GetAsFloat: TsExprFloat;
function GetAsInteger: Int64;
function GetAsString: String;
function GetRPNFormula: TsRPNFormula;
function MatchNodes(Todo, Match: TsExprNode): TsExprNode;
procedure SetBuiltIns(const AValue: TsBuiltInExprCategories);
procedure SetIdentifiers(const AValue: TsExprIdentifierDefs);
procedure SetRPNFormula(const AFormula: TsRPNFormula);
protected
class function BuiltinExpressionManager: TsBuiltInExpressionManager;
procedure ParserError(Msg: String);
procedure SetExpression(const AValue: String); virtual;
procedure CheckResultType(const Res: TsExpressionResult;
AType: TsResultType); inline;
function CurrentToken: String;
function GetToken: TsTokenType;
function Level1: TsExprNode;
function Level2: TsExprNode;
function Level3: TsExprNode;
function Level4: TsExprNode;
function Level5: TsExprNode;
function Level6: TsExprNode;
function Primitive: TsExprNode;
function TokenType: TsTokenType;
procedure CreateHashList;
property Scanner: TsExpressionScanner read FScanner;
property ExprNode: TsExprNode read FExprNode;
property Dirty: Boolean read FDirty;
public
constructor Create(AWorksheet: TsWorksheet); virtual;
destructor Destroy; override;
function IdentifierByName(AName: ShortString): TsExprIdentifierDef; virtual;
procedure Clear;
function BuildStringFormula: String;
function Evaluate: TsExpressionResult;
procedure EvaluateExpression(var Result: TsExpressionResult);
function ResultType: TsResultType;
property AsFloat: TsExprFloat read GetAsFloat;
property AsInteger: Int64 read GetAsInteger;
property AsString: String read GetAsString;
property AsBoolean: Boolean read GetAsBoolean;
property AsDateTime: TDateTime read GetAsDateTime;
// The expression to parse
property Expression: String read FExpression write SetExpression;
property RPNFormula: TsRPNFormula read GetRPNFormula write SetRPNFormula;
property Identifiers: TsExprIdentifierDefs read FIdentifiers write SetIdentifiers;
property BuiltIns: TsBuiltInExprCategories read FBuiltIns write SetBuiltIns;
property Worksheet: TsWorksheet read FWorksheet;
end;
TsSpreadsheetParser = class(TsExpressionParser)
public
constructor Create(AWorksheet: TsWorksheet); override;
end;
{ TsBuiltInExpressionManager }
TsBuiltInExpressionManager = class(TComponent)
private
FDefs: TsExprIdentifierDefs;
function GetCount: Integer;
function GetI(AIndex: Integer): TsBuiltInExprIdentifierDef;
protected
property Defs: TsExprIdentifierDefs read FDefs;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function IndexOfIdentifier(const AName: ShortString): Integer;
function FindIdentifier(const AName: ShortString): TsBuiltInExprIdentifierDef;
function IdentifierByExcelCode(const AExcelCode: Integer): TsBuiltInExprIdentifierDef;
function IdentifierByName(const AName: ShortString): TsBuiltInExprIdentifierDef;
function AddVariable(const ACategory: TsBuiltInExprCategory; const AName: ShortString;
AResultType: TsResultType; AValue: String): TsBuiltInExprIdentifierDef;
function AddBooleanVariable(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; AValue: Boolean): TsBuiltInExprIdentifierDef;
function AddIntegerVariable(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; AValue: Integer): TsBuiltInExprIdentifierDef;
function AddFloatVariable(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; AValue: TsExprFloat): TsBuiltInExprIdentifierDef;
function AddStringVariable(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; AValue: String): TsBuiltInExprIdentifierDef;
function AddDateTimeVariable(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; AValue: TDateTime): TsBuiltInExprIdentifierDef;
function AddFunction(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; const AResultType: Char; const AParamTypes: String;
const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack): TsBuiltInExprIdentifierDef;
function AddFunction(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; const AResultType: Char; const AParamTypes: String;
const AExcelCode: Integer; ACallBack: TsExprFunctionEvent): TsBuiltInExprIdentifierDef;
property IdentifierCount: Integer read GetCount;
property Identifiers[AIndex: Integer]: TsBuiltInExprIdentifierDef read GetI;
end;
EExprParser = class(Exception);
function TokenName(AToken: TsTokenType): String;
function ResultTypeName(AResult: TsResultType): String;
function CharToResultType(C: Char): TsResultType;
function BuiltinIdentifiers: TsBuiltInExpressionManager;
procedure RegisterStdBuiltins(AManager: TsBuiltInExpressionManager);
function ArgToBoolean(Arg: TsExpressionResult): Boolean;
function ArgToCell(Arg: TsExpressionResult): PCell;
function ArgToDateTime(Arg: TsExpressionResult): TDateTime;
function ArgToInt(Arg: TsExpressionResult): Integer;
function ArgToFloat(Arg: TsExpressionResult): TsExprFloat;
function ArgToString(Arg: TsExpressionResult): String;
procedure ArgsToFloatArray(const Args: TsExprParameterArray; out AData: TsExprFloatArray);
function BooleanResult(AValue: Boolean): TsExpressionResult;
function DateTimeResult(AValue: TDateTime): TsExpressionResult;
function EmptyResult: TsExpressionResult;
function ErrorResult(const AValue: TsErrorValue): TsExpressionResult;
function FloatResult(const AValue: TsExprFloat): TsExpressionResult;
function IntegerResult(const AValue: Integer): TsExpressionResult;
function StringResult(const AValue: String): TsExpressionResult;
procedure RegisterFunction(const AName: ShortString; const AResultType: Char;
const AParamTypes: String; const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack);
const
AllBuiltIns = [bcMath, bcStatistics, bcStrings, bcLogical, bcDateTime, bcLookup,
bcInfo, bcUser];
var
ExprFormatSettings: TFormatSettings;
implementation
uses
typinfo, math, lazutf8, dateutils, xlsconst, fpsutils;
const
cNull = #0;
cDoubleQuote = '"';
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';
SErrNoUPlus = 'Cannot perform unary plus operation on type %s: %s';
SErrNoNOTOperation = 'Cannot perform NOT operation on expression of type %s: %s';
SErrNoPercentOperation = 'Cannot perform percent operation 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';
SErrCircularReference = 'Circular reference found when calculating worksheet formulas';
{ ---------------------------------------------------------------------
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: TsTokenType): String;
begin
Result := GetEnumName(TypeInfo(TsTokenType), ord(AToken));
end;
function ResultTypeName(AResult: TsResultType): String;
begin
Result := GetEnumName(TypeInfo(TsResultType), ord(AResult));
end;
function CharToResultType(C: Char): TsResultType;
begin
case Upcase(C) of
'S' : Result := rtString;
'D' : Result := rtDateTime;
'B' : Result := rtBoolean;
'I' : Result := rtInteger;
'F' : Result := rtFloat;
'R' : Result := rtCellRange;
'C' : Result := rtCell;
'?' : Result := rtAny;
else
RaiseParserError(SErrInvalidResultCharacter, [C]);
end;
end;
var
BuiltIns: TsBuiltInExpressionManager = nil;
function BuiltinIdentifiers: TsBuiltInExpressionManager;
begin
If (BuiltIns = nil) then
BuiltIns := TsBuiltInExpressionManager.Create(nil);
Result := BuiltIns;
end;
procedure FreeBuiltIns;
begin
FreeAndNil(Builtins);
end;
{------------------------------------------------------------------------------}
{ TsExpressionScanner }
{------------------------------------------------------------------------------}
constructor TsExpressionScanner.Create;
begin
Source := '';
end;
function TsExpressionScanner.DoDelimiter: TsTokenType;
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 := ttNotEqual
else
Result := ttLessThanEqual;
end
else
case D of
'+' : Result := ttPlus;
'-' : Result := ttMinus;
'*' : Result := ttMul;
'/' : Result := ttDiv;
'^' : Result := ttPower;
'%' : Result := ttPercent;
'&' : Result := ttConcat;
'<' : Result := ttLessThan;
'>' : Result := ttLargerThan;
'=' : Result := ttEqual;
'(' : Result := ttLeft;
')' : Result := ttRight;
',' : Result := ttComma;
else
ScanError(Format(SUnknownDelimiter, [D]));
end;
end;
function TsExpressionScanner.DoIdentifier: TsTokenType;
var
C: Char;
S: String;
row, row2: Cardinal;
col, col2: Cardinal;
flags: TsRelFlags;
begin
C := CurrentChar;
while (not IsWordDelim(C)) and (C <> cNull) do
begin
FToken := FToken + C;
C := NextPos;
end;
S := LowerCase(Token);
if ParseCellString(S, row, col, flags) and (C <> '(') then
Result := ttCell
else if ParseCellRangeString(S, row, col, row2, col2, flags) and (C <> '(') then
Result := ttCellRange
else if (S = 'true') and (C <> '(') then
Result := ttTrue
else if (S = 'false') and (C <> '(') then
Result := ttFalse
else
Result := ttIdentifier;
end;
function TsExpressionScanner.DoNumber: TsTokenType;
var
C: Char;
X: TsExprFloat;
prevC: Char;
begin
C := CurrentChar;
prevC := #0;
while (not IsWordDelim(C) or (prevC = 'E')) and (C <> cNull) do
begin
if not ( IsDigit(C)
or ((FToken <> '') and (Upcase(C) = 'E'))
or ((FToken <> '') and (C in ['+', '-']) and (prevC = 'E'))
)
then
ScanError(Format(SErrInvalidNumberChar, [C]));
FToken := FToken+C;
prevC := Upcase(C);
C := NextPos;
end;
if not TryStrToFloat(FToken, X, ExprFormatSettings) then
ScanError(Format(SErrInvalidNumber, [FToken]));
Result := ttNumber;
end;
function TsExpressionScanner.DoString: TsTokenType;
function TerminatingChar(C: Char): boolean;
begin
Result := (C = cNull)
or ((C = cDoubleQuote) and
not ((FPos < LSource) and (FSource[FPos+1] = cDoubleQuote)));
end;
var
C: Char;
begin
FToken := '';
C := NextPos;
while not TerminatingChar(C) do
begin
FToken := FToken+C;
if C = cDoubleQuote then
NextPos;
C := NextPos;
end;
if (C = cNull) then
ScanError(SBadQuotes);
Result := ttString;
FTokenType := Result;
NextPos;
end;
function TsExpressionScanner.GetCurrentChar: Char;
begin
if FChar <> nil then
Result := FChar^
else
Result := #0;
end;
function TsExpressionScanner.GetToken: TsTokenType;
var
C: Char;
begin
FToken := '';
SkipWhiteSpace;
C := FChar^;
if c = cNull then
Result := ttEOF
else if IsDelim(C) then
Result := DoDelimiter
else if (C = cDoubleQuote) then
Result := DoString
else if IsDigit(C) then
Result := DoNumber
else if IsAlpha(C) or (C = '$') then
Result := DoIdentifier
else
ScanError(Format(SErrUnknownCharacter, [FPos, C]));
FTokenType := Result;
end;
function TsExpressionScanner.IsAlpha(C: Char): Boolean;
begin
Result := C in ['A'..'Z', 'a'..'z'];
end;
function TsExpressionScanner.IsDelim(C: Char): Boolean;
begin
Result := C in Delimiters;
end;
function TsExpressionScanner.IsDigit(C: Char): Boolean;
begin
Result := C in Digits;
end;
function TsExpressionScanner.IsWordDelim(C: Char): Boolean;
begin
Result := C in WordDelimiters;
end;
function TsExpressionScanner.NextPos: Char;
begin
Inc(FPos);
Inc(FChar);
Result := FChar^;
end;
procedure TsExpressionScanner.ScanError(Msg: String);
begin
raise EExprScanner.Create(Msg)
end;
procedure TsExpressionScanner.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;
procedure TsExpressionScanner.SkipWhiteSpace;
begin
while (FChar^ in WhiteSpace) and (FPos <= LSource) do
NextPos;
end;
{------------------------------------------------------------------------------}
{ TsExpressionParser }
{------------------------------------------------------------------------------}
constructor TsExpressionParser.Create(AWorksheet: TsWorksheet);
begin
inherited Create;
FWorksheet := AWorksheet;
FIdentifiers := TsExprIdentifierDefs.Create(TsExprIdentifierDef);
FIdentifiers.FParser := Self;
FScanner := TsExpressionScanner.Create;
FHashList := TFPHashObjectList.Create(False);
end;
destructor TsExpressionParser.Destroy;
begin
FreeAndNil(FHashList);
FreeAndNil(FExprNode);
FreeAndNil(FIdentifiers);
FreeAndNil(FScanner);
inherited Destroy;
end;
function TsExpressionParser.BuildStringFormula: String;
begin
if FExprNode = nil then
Result := ''
else
Result := FExprNode.AsString;
end;
class function TsExpressionParser.BuiltinExpressionManager: TsBuiltInExpressionManager;
begin
Result := BuiltinIdentifiers;
end;
procedure TsExpressionParser.CheckEOF;
begin
if (TokenType = ttEOF) then
ParserError(SErrUnexpectedEndOfExpression);
end;
{ If the result types differ, they are converted to a common type if possible. }
procedure TsExpressionParser.CheckNodes(var ALeft, ARight: TsExprNode);
begin
ALeft := MatchNodes(ALeft, ARight);
ARight := MatchNodes(ARight, ALeft);
end;
procedure TsExpressionParser.CheckResultType(const Res: TsExpressionResult;
AType: TsResultType); inline;
begin
if (Res.ResultType <> AType) then
RaiseParserError(SErrInvalidResultType, [ResultTypeName(Res.ResultType)]);
end;
procedure TsExpressionParser.Clear;
begin
FExpression := '';
FHashList.Clear;
FreeAndNil(FExprNode);
end;
function TsExpressionParser.ConvertNode(ToDo: TsExprNode;
ToType: TsResultType): TsExprNode;
begin
Result := ToDo;
case ToDo.NodeType of
rtInteger :
case ToType of
rtFloat : Result := TsIntToFloatExprNode.Create(Result);
rtDateTime : Result := TsIntToDateTimeExprNode.Create(Result);
end;
rtFloat :
case ToType of
rtDateTime : Result := TsFloatToDateTimeExprNode.Create(Result);
end;
end;
end;
procedure TsExpressionParser.CreateHashList;
var
ID: TsExprIdentifierDef;
BID: TsBuiltInExprIdentifierDef;
i: Integer;
M: TsBuiltInExpressionManager;
begin
FHashList.Clear;
// Builtins
M := BuiltinExpressionManager;
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(UpperCase(BID.Name), BID);
end;
// User
for i:=0 to FIdentifiers.Count-1 do
begin
ID := FIdentifiers[i];
FHashList.Add(UpperCase(ID.Name), ID);
end;
FDirty := False;
end;
function TsExpressionParser.CurrentToken: String;
begin
Result := FScanner.Token;
end;
function TsExpressionParser.Evaluate: TsExpressionResult;
begin
EvaluateExpression(Result);
end;
procedure TsExpressionParser.EvaluateExpression(var Result: TsExpressionResult);
begin
if (FExpression = '') then
ParserError(SErrInExpressionEmpty);
if not Assigned(FExprNode) then
ParserError(SErrInExpression);
FExprNode.GetNodeValue(Result);
end;
function TsExpressionParser.GetAsBoolean: Boolean;
var
Res: TsExpressionResult;
begin
EvaluateExpression(Res);
CheckResultType(Res, rtBoolean);
Result := Res.ResBoolean;
end;
function TsExpressionParser.GetAsDateTime: TDateTime;
var
Res: TsExpressionResult;
begin
EvaluateExpression(Res);
CheckResultType(Res, rtDateTime);
Result := Res.ResDatetime;
end;
function TsExpressionParser.GetAsFloat: TsExprFloat;
var
Res: TsExpressionResult;
begin
EvaluateExpression(Res);
CheckResultType(Res, rtFloat);
Result := Res.ResFloat;
end;
function TsExpressionParser.GetAsInteger: Int64;
var
Res: TsExpressionResult;
begin
EvaluateExpression(Res);
CheckResultType(Res, rtInteger);
Result := Res.ResInteger;
end;
function TsExpressionParser.GetAsString: String;
var
Res: TsExpressionResult;
begin
EvaluateExpression(Res);
CheckResultType(Res, rtString);
Result := Res.ResString;
end;
function TsExpressionParser.GetRPNFormula: TsRPNFormula;
begin
Result := CreateRPNFormula(FExprNode.AsRPNItem(nil), true);
end;
function TsExpressionParser.GetToken: TsTokenType;
begin
Result := FScanner.GetToken;
end;
function TsExpressionParser.IdentifierByName(AName: ShortString): TsExprIdentifierDef;
var
s: String;
begin
if FDirty then
CreateHashList;
s := FHashList.NameOfIndex(0);
s := FHashList.NameOfIndex(25);
s := FHashList.NameOfIndex(36);
Result := TsExprIdentifierDef(FHashList.Find(UpperCase(AName)));
end;
function TsExpressionParser.Level1: TsExprNode;
var
tt: TsTokenType;
Right: TsExprNode;
begin
{$ifdef debugexpr}Writeln('Level 1 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
{
if TokenType = ttNot then
begin
GetToken;
CheckEOF;
Right := Level2;
Result := TsNotExprNode.Create(Right);
end
else
}
Result := Level2;
{
try
if TokenType = ttPower then
begin
tt := Tokentype;
GetToken;
CheckEOF;
Right := Level2;
Result := TsPowerExprNode.Create(Result, Right);
end;
except
Result.Free;
raise;
end;
}
{
try
while (TokenType in [ttAnd, ttOr, ttXor]) do
begin
tt := TokenType;
GetToken;
CheckEOF;
Right := Level2;
case tt of
ttOr : Result := TsBinaryOrExprNode.Create(Result, Right);
ttAnd : Result := TsBinaryAndExprNode.Create(Result, Right);
ttXor : Result := TsBinaryXorExprNode.Create(Result, Right);
else
ParserError(SErrUnknownBooleanOp)
end;
end;
except
Result.Free;
raise;
end;
}
end;
function TsExpressionParser.Level2: TsExprNode;
var
right: TsExprNode;
tt: TsTokenType;
C: TsBinaryOperationExprNodeClass;
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 := TsLessExprNode;
ttLessthanEqual : C := TsLessEqualExprNode;
ttLargerThan : C := TsGreaterExprNode;
ttLargerThanEqual : C := TsGreaterEqualExprNode;
ttEqual : C := TsEqualExprNode;
ttNotEqual : C := TsNotEqualExprNode;
else
ParserError(SErrUnknownComparison)
end;
Result := C.Create(Result, right);
end;
except
Result.Free;
raise;
end;
end;
function TsExpressionParser.Level3: TsExprNode;
var
tt: TsTokenType;
right: TsExprNode;
begin
{$ifdef debugexpr} Writeln('Level 3 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
Result := Level4;
try
while TokenType in [ttPlus, ttMinus, ttConcat] do begin
tt := TokenType;
GetToken;
CheckEOF;
right := Level4;
CheckNodes(Result, right);
case tt of
ttPlus : Result := TsAddExprNode.Create(Result, right);
ttMinus : Result := TsSubtractExprNode.Create(Result, right);
ttConcat: Result := TsConcatExprNode.Create(Result, right);
end;
end;
except
Result.Free;
raise;
end;
end;
function TsExpressionParser.Level4: TsExprNode;
var
tt: TsTokenType;
right: TsExprNode;
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 := TsMultiplyExprNode.Create(Result, right);
ttDiv : Result := TsDivideExprNode.Create(Result, right);
end;
end;
except
Result.Free;
Raise;
end;
end;
function TsExpressionParser.Level5: TsExprNode;
var
isPlus, isMinus: Boolean;
tt: TsTokenType;
begin
{$ifdef debugexpr} Writeln('Level 5 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
isPlus := false;
isMinus := false;
if (TokenType in [ttPlus, ttMinus]) then
begin
isPlus := (TokenType = ttPlus);
isMinus := (TokenType = ttMinus);
GetToken;
end;
Result := Level6;
if isPlus then
Result := TsUPlusExprNode.Create(Result);
if isMinus then
Result := TsUMinusExprNode.Create(Result);
end;
function TsExpressionParser.Level6: TsExprNode;
var
tt: TsTokenType;
Right: TsExprNode;
begin
{$ifdef debugexpr} Writeln('Level 6 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
if (TokenType = ttLeft) then
begin
GetToken;
Result := TsParenthesisExprNode.Create(Level1);
try
if (TokenType <> ttRight) then
ParserError(Format(SErrBracketExpected, [SCanner.Pos, CurrentToken]));
GetToken;
except
Result.Free;
raise;
end;
end
else
Result := Primitive;
if TokenType = ttPower then
begin
try
CheckEOF;
tt := Tokentype;
GetToken;
Right := Primitive;
CheckNodes(Result, right);
Result := TsPowerExprNode.Create(Result, Right);
//GetToken;
except
Result.Free;
raise;
end;
end;
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 TsExpressionParser.MatchNodes(ToDo, Match: TsExprNode): TsExprNode;
var
TT, MT : TsResultType;
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;
procedure TsExpressionParser.ParserError(Msg: String);
begin
raise EExprParser.Create(Msg);
end;
function TsExpressionParser.Primitive: TsExprNode;
var
I: Int64;
X: TsExprFloat;
lCount: Integer;
ID: TsExprIdentifierDef;
Args: TsExprArgumentArray;
AI: Integer;
cell: PCell;
optional: Boolean;
token: String;
begin
{$ifdef debugexpr} Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
SetLength(Args, 0);
if (TokenType = ttNumber) then
begin
if TryStrToInt64(CurrentToken, I) then
Result := TsConstExprNode.CreateInteger(I)
else
begin
if TryStrToFloat(CurrentToken, X, ExprFormatSettings) then
Result := TsConstExprNode.CreateFloat(X)
else
ParserError(Format(SErrInvalidFloat, [CurrentToken]));
end;
end
else if (TokenType = ttTrue) then
Result := TsConstExprNode.CreateBoolean(true)
else if (TokenType = ttFalse) then
Result := TsConstExprNode.CreateBoolean(false)
else if (TokenType = ttString) then
Result := TsConstExprNode.CreateString(CurrentToken)
else if (TokenType = ttCell) then
Result := TsCellExprNode.Create(FWorksheet, CurrentToken)
else if (TokenType = ttCellRange) then
Result := TsCellRangeExprNode.Create(FWorksheet, CurrentToken)
else if not (TokenType in [ttIdentifier{, ttIf}]) then
ParserError(Format(SerrUnknownTokenAtPos, [Scanner.Pos, CurrentToken]))
else
begin
token := Uppercase(CurrentToken);
ID := self.IdentifierByName(token);
if (ID = nil) then
ParserError(Format(SErrUnknownIdentifier, [token]));
if (ID.IdentifierType in [itFunctionCallBack, itFunctionHandler]) then
begin
lCount := ID.ArgumentCount;
if lCount = 0 then // we have to handle the () here, it will be skipped below.
begin
GetToken;
if (TokenType <> ttLeft) then
ParserError(Format(SErrLeftBracketExpected, [Scanner.Pos, CurrentToken]));
GetToken;
if (TokenType <> ttRight) then
ParserError(Format(SErrBracketExpected, [Scanner.Pos, CurrentToken]));
SetLength(Args, 0);
end;
end
else
lCount := 0;
// Parse arguments.
// Negative is for variable number of arguments, where Abs(value) is the minimum number of arguments
if (lCount <> 0) then
begin
GetToken;
if (TokenType <> ttLeft) then
ParserError(Format(SErrLeftBracketExpected, [Scanner.Pos, CurrentToken]));
SetLength(Args, abs(lCount));
AI := 0;
try
repeat
GetToken;
// Check if we must enlarge the argument array
if (lCount < 0) and (AI = Length(Args)) then
begin
SetLength(Args, AI+1);
Args[AI] := nil;
end;
Args[AI] := Level1;
inc(AI);
optional := ID.IsOptionalArgument(AI+1);
if not optional then
begin
if (TokenType <> ttComma) then
if (AI < abs(lCount)) then
ParserError(Format(SErrCommaExpected, [Scanner.Pos, CurrentToken]))
end;
until (AI = lCount) or (((lCount < 0) or optional) and (TokenType = ttRight));
if TokenType <> ttRight then
ParserError(Format(SErrBracketExpected, [Scanner.Pos, CurrentToken]));
if AI < abs(lCount) then
SetLength(Args, AI);
except
on E: Exception do
begin
dec(AI);
while (AI >= 0) do
begin
FreeAndNil(Args[Ai]);
dec(AI);
end;
raise;
end;
end;
end;
case ID.IdentifierType of
itVariable : Result := TsVariableExprNode.CreateIdentifier(ID);
itFunctionCallBack : Result := TsFunctionCallBackExprNode.CreateFunction(ID, Args);
itFunctionHandler : Result := TFPFunctionEventHandlerExprNode.CreateFunction(ID, Args);
end;
end;
GetToken;
if TokenType = ttPercent then begin
Result := TsPercentExprNode.Create(Result);
GetToken;
end;
end;
function TsExpressionParser.ResultType: TsResultType;
begin
if not Assigned(FExprNode) then
ParserError(SErrInExpression);
Result := FExprNode.NodeType;;
end;
procedure TsExpressionParser.SetBuiltIns(const AValue: TsBuiltInExprCategories);
begin
if FBuiltIns = AValue then
exit;
FBuiltIns := AValue;
FDirty := true;
end;
procedure TsExpressionParser.SetExpression(const AValue: String);
begin
if FExpression = AValue then
exit;
FExpression := AValue;
if (AValue <> '') and (AValue[1] = '=') then
FScanner.Source := Copy(AValue, 2, Length(AValue))
else
FScanner.Source := AValue;
FreeAndNil(FExprNode);
if (FExpression <> '') then
begin
GetToken;
FExprNode := Level1;
if (TokenType <> ttEOF) then
ParserError(Format(SErrUnterminatedExpression, [Scanner.Pos, CurrentToken]));
FExprNode.Check;
end;
end;
procedure TsExpressionParser.SetIdentifiers(const AValue: TsExprIdentifierDefs);
begin
FIdentifiers.Assign(AValue)
end;
procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula);
procedure CreateNodeFromRPN(var ANode: TsExprNode; var AIndex: Integer);
var
node: TsExprNode;
left: TsExprNode;
right: TsExprNode;
operand: TsExprNode;
fek: TFEKind;
r,c, r2,c2: Cardinal;
flags: TsRelFlags;
ID: TsExprIdentifierDef;
i, n: Integer;
args: TsExprArgumentArray;
begin
if AIndex < 0 then
exit;
fek := AFormula[AIndex].ElementKind;
case fek of
fekCell, fekCellRef:
begin
r := AFormula[AIndex].Row;
c := AFormula[AIndex].Col;
flags := AFormula[AIndex].RelFlags;
ANode := TsCellExprNode.Create(FWorksheet, r, c, flags);
dec(AIndex);
end;
fekCellRange:
begin
r := AFormula[AIndex].Row;
c := AFormula[AIndex].Col;
r2 := AFormula[AIndex].Row2;
c2 := AFormula[AIndex].Col2;
flags := AFormula[AIndex].RelFlags;
ANode := TsCellRangeExprNode.Create(FWorksheet, r, c, r2, c2, flags);
dec(AIndex);
end;
fekNum:
begin
ANode := TsConstExprNode.CreateFloat(AFormula[AIndex].DoubleValue);
dec(AIndex);
end;
fekInteger:
begin
ANode := TsConstExprNode.CreateInteger(AFormula[AIndex].IntValue);
dec(AIndex);
end;
fekString:
begin
ANode := TsConstExprNode.CreateString(AFormula[AIndex].StringValue);
dec(AIndex);
end;
fekBool:
begin
ANode := TsConstExprNode.CreateBoolean(AFormula[AIndex].DoubleValue <> 0.0);
dec(AIndex);
end;
fekErr:
begin
ANode := TsConstExprNode.CreateError(TsErrorValue(AFormula[AIndex].IntValue));
dec(AIndex);
end;
// unary operations
fekPercent, fekUMinus, fekUPlus, fekParen:
begin
dec(AIndex);
CreateNodeFromRPN(operand, AIndex);
case fek of
fekPercent : ANode := TsPercentExprNode.Create(operand);
fekUMinus : ANode := TsUMinusExprNode.Create(operand);
fekUPlus : ANode := TsUPlusExprNode.Create(operand);
fekParen : ANode := TsParenthesisExprNode.Create(operand);
end;
end;
// binary operations
fekAdd, fekSub, fekMul, fekDiv,
fekPower, fekConcat,
fekEqual, fekNotEqual,
fekGreater, fekGreaterEqual,
fekLess, fekLessEqual:
begin
dec(AIndex);
CreateNodeFromRPN(right, AIndex);
CreateNodeFromRPN(left, AIndex);
CheckNodes(left, right);
case fek of
fekAdd : ANode := TsAddExprNode.Create(left, right);
fekSub : ANode := TsSubtractExprNode.Create(left, right);
fekMul : ANode := TsMultiplyExprNode.Create(left, right);
fekDiv : ANode := TsDivideExprNode.Create(left, right);
fekPower : ANode := TsPowerExprNode.Create(left, right);
fekConcat : ANode := tsConcatExprNode.Create(left, right);
fekEqual : ANode := TsEqualExprNode.Create(left, right);
fekNotEqual : ANode := TsNotEqualExprNode.Create(left, right);
fekGreater : ANode := TsGreaterExprNode.Create(left, right);
fekGreaterEqual: ANode := TsGreaterEqualExprNode.Create(left, right);
fekLess : ANode := TsLessExprNode.Create(left, right);
fekLessEqual : ANode := tsLessEqualExprNode.Create(left, right);
end;
end;
// functions
fekFunc:
begin
ID := self.IdentifierByName(AFormula[AIndex].FuncName);
if ID = nil then
begin
ParserError(Format(SErrUnknownIdentifier,[AFormula[AIndex].FuncName]));
dec(AIndex);
end else
begin
if ID.HasFixedArgumentCount then
n := ID.ArgumentCount
else
n := AFormula[AIndex].ParamsNum;
dec(AIndex);
SetLength(args, n);
for i:=n-1 downto 0 do
CreateNodeFromRPN(args[i], AIndex);
case ID.IdentifierType of
itVariable : ANode := TsVariableExprNode.CreateIdentifier(ID);
itFunctionCallBack : ANode := TsFunctionCallBackExprNode.CreateFunction(ID, args);
itFunctionHandler : ANode := TFPFunctionEventHandlerExprNode.CreateFunction(ID, args);
end;
end;
end;
end; //case
end; //begin
var
index: Integer;
node: TsExprNode;
begin
FExpression := '';
FreeAndNil(FExprNode);
index := Length(AFormula)-1;
CreateNodeFromRPN(FExprNode, index);
if Assigned(FExprNode) then FExprNode.Check;
end;
function TsExpressionParser.TokenType: TsTokenType;
begin
Result := FScanner.TokenType;
end;
{------------------------------------------------------------------------------}
{ TsSpreadsheetParser }
{------------------------------------------------------------------------------}
constructor TsSpreadsheetParser.Create(AWorksheet: TsWorksheet);
begin
inherited Create(AWorksheet);
BuiltIns := AllBuiltIns;
end;
{------------------------------------------------------------------------------}
{ TsExprIdentifierDefs }
{------------------------------------------------------------------------------}
function TsExprIdentifierDefs.AddBooleanVariable(const AName: ShortString;
AValue: Boolean): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.IdentifierType := itVariable;
Result.Name := AName;
Result.ResultType := rtBoolean;
Result.FValue.ResBoolean := AValue;
end;
function TsExprIdentifierDefs.AddDateTimeVariable(const AName: ShortString;
AValue: TDateTime): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.IdentifierType := itVariable;
Result.Name := AName;
Result.ResultType := rtDateTime;
Result.FValue.ResDateTime := AValue;
end;
function TsExprIdentifierDefs.AddFloatVariable(const AName: ShortString;
AValue: TsExprFloat): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.IdentifierType := itVariable;
Result.Name := AName;
Result.ResultType := rtFloat;
Result.FValue.ResFloat := AValue;
end;
function TsExprIdentifierDefs.AddFunction(const AName: ShortString;
const AResultType: Char; const AParamTypes: String; const AExcelCode: Integer;
ACallBack: TsExprFunctionCallBack): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.Name := AName;
Result.IdentifierType := itFunctionCallBack;
Result.ResultType := CharToResultType(AResultType);
Result.ExcelCode := AExcelCode;
Result.FOnGetValueCB := ACallBack;
if (Length(AParamTypes) > 0) and (AParamTypes[Length(AParamTypes)]='+') then
begin
Result.ParameterTypes := Copy(AParamTypes, 1, Length(AParamTypes)-1);
Result.VariableArgumentCount := true;
end else
Result.ParameterTypes := AParamTypes;
end;
function TsExprIdentifierDefs.AddFunction(const AName: ShortString;
const AResultType: Char; const AParamTypes: String; const AExcelCode: Integer;
ACallBack: TsExprFunctionEvent): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.Name := AName;
Result.IdentifierType := itFunctionHandler;
Result.ResultType := CharToResultType(AResultType);
Result.ExcelCode := AExcelCode;
Result.FOnGetValue := ACallBack;
if (Length(AParamTypes) > 0) and (AParamTypes[Length(AParamTypes)]='+') then
begin
Result.ParameterTypes := Copy(AParamTypes, 1, Length(AParamTypes)-1);
Result.VariableArgumentCount := true;
end else
Result.ParameterTypes := AParamTypes;
end;
function TsExprIdentifierDefs.AddIntegerVariable(const AName: ShortString;
AValue: Integer): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.IdentifierType := itVariable;
Result.Name := AName;
Result.ResultType := rtInteger;
Result.FValue.ResInteger := AValue;
end;
function TsExprIdentifierDefs.AddStringVariable(const AName: ShortString;
AValue: String): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.IdentifierType := itVariable;
Result.Name := AName;
Result.ResultType := rtString;
Result.FValue.ResString := AValue;
end;
function TsExprIdentifierDefs.AddVariable(const AName: ShortString;
AResultType: TsResultType; AValue: String): TsExprIdentifierDef;
begin
Result := Add as TsExprIdentifierDef;
Result.IdentifierType := itVariable;
Result.Name := AName;
Result.ResultType := AResultType;
Result.Value := AValue;
end;
function TsExprIdentifierDefs.FindIdentifier(const AName: ShortString
): TsExprIdentifierDef;
var
I: Integer;
begin
I := IndexOfIdentifier(AName);
if (I = -1) then
Result := nil
else
Result := GetI(I);
end;
function TsExprIdentifierDefs.GetI(AIndex : Integer): TsExprIdentifierDef;
begin
Result := TsExprIdentifierDef(Items[AIndex]);
end;
function TsExprIdentifierDefs.IdentifierByExcelCode(const AExcelCode: Integer
): TsExprIdentifierDef;
var
I: Integer;
begin
I := IndexOfIdentifier(AExcelCode);
if I = -1 then
Result := nil
else
Result := GetI(I);
end;
function TsExprIdentifierDefs.IdentifierByName(const AName: ShortString
): TsExprIdentifierDef;
begin
Result := FindIdentifier(AName);
if (Result = nil) then
RaiseParserError(SErrUnknownIdentifier, [AName]);
end;
function TsExprIdentifierDefs.IndexOfIdentifier(const AName: ShortString): Integer;
begin
Result := Count-1;
while (Result >= 0) and (CompareText(GetI(Result).Name, AName) <> 0) do
dec(Result);
end;
function TsExprIdentifierDefs.IndexOfIdentifier(const AExcelCode: Integer): Integer;
var
ID: TsExprIdentifierDef;
begin
Result := Count-1;
while (Result >= 0) do begin
ID := GetI(Result);
if ID.ExcelCode = AExcelCode then exit;
dec(Result);
end;
{
while (Result >= 0) and (GetI(Result).ExcelCode = AExcelCode) do
dec(Result);
}
end;
procedure TsExprIdentifierDefs.SetI(AIndex: Integer;
const AValue: TsExprIdentifierDef);
begin
Items[AIndex] := AValue;
end;
procedure TsExprIdentifierDefs.Update(Item: TCollectionItem);
begin
if Assigned(FParser) then
FParser.FDirty := true;
end;
{------------------------------------------------------------------------------}
{ TsExprIdentifierDef }
{------------------------------------------------------------------------------}
function TsExprIdentifierDef.ArgumentCount: Integer;
begin
if FVariableArgumentCount then
Result := -Length(FArgumentTypes)
else
Result := Length(FArgumentTypes);
end;
procedure TsExprIdentifierDef.Assign(Source: TPersistent);
var
EID: TsExprIdentifierDef;
begin
if (Source is TsExprIdentifierDef) then
begin
EID := Source as TsExprIdentifierDef;
FStringValue := EID.FStringValue;
FValue := EID.FValue;
FArgumentTypes := EID.FArgumentTypes;
FVariableArgumentCount := EID.FVariableArgumentCount;
FExcelCode := EID.ExcelCode;
FIDType := EID.FIDType;
FName := EID.FName;
FOnGetValue := EID.FOnGetValue;
FOnGetValueCB := EID.FOnGetValueCB;
end
else
inherited Assign(Source);
end;
procedure TsExprIdentifierDef.CheckResultType(const AType: TsResultType);
begin
if (FValue.ResultType <> AType) then
RaiseParserError(SErrInvalidResultType, [ResultTypeName(AType)])
end;
procedure TsExprIdentifierDef.CheckVariable;
begin
if Identifiertype <> itVariable then
RaiseParserError(SErrNotVariable, [Name]);
end;
function TsExprIdentifierDef.GetAsBoolean: Boolean;
begin
CheckResultType(rtBoolean);
CheckVariable;
Result := FValue.ResBoolean;
end;
function TsExprIdentifierDef.GetAsDateTime: TDateTime;
begin
CheckResultType(rtDateTime);
CheckVariable;
Result := FValue.ResDateTime;
end;
function TsExprIdentifierDef.GetAsFloat: TsExprFloat;
begin
CheckResultType(rtFloat);
CheckVariable;
Result := FValue.ResFloat;
end;
function TsExprIdentifierDef.GetAsInteger: Int64;
begin
CheckResultType(rtInteger);
CheckVariable;
Result := FValue.ResInteger;
end;
function TsExprIdentifierDef.GetAsString: String;
begin
CheckResultType(rtString);
CheckVariable;
Result := FValue.ResString;
end;
function TsExprIdentifierDef.GetResultType: TsResultType;
begin
Result := FValue.ResultType;
end;
function TsExprIdentifierDef.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, ExprFormatSettings);
rtDateTime : Result := FormatDateTime('cccc', FValue.ResDateTime);
rtString : Result := FValue.ResString;
end;
end;
{ Returns true if the epxression has a fixed number of arguments. }
function TsExprIdentifierDef.HasFixedArgumentCount: Boolean;
var
i: Integer;
begin
if FVariableArgumentCount then
Result := false
else
begin
for i:= 1 to Length(FArgumentTypes) do
if IsOptionalArgument(i) then
begin
Result := false;
exit;
end;
Result := true;
end;
end;
{ Checks whether an argument is optional. Index number starts at 1.
Optional arguments are lower-case characters in the argument list. }
function TsExprIdentifierDef.IsOptionalArgument(AIndex: Integer): Boolean;
begin
Result := (AIndex <= Length(FArgumentTypes))
and (UpCase(FArgumentTypes[AIndex]) <> FArgumentTypes[AIndex]);
end;
procedure TsExprIdentifierDef.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 TsExprIdentifierDef.SetAsBoolean(const AValue: Boolean);
begin
CheckVariable;
CheckResultType(rtBoolean);
FValue.ResBoolean := AValue;
end;
procedure TsExprIdentifierDef.SetAsDateTime(const AValue: TDateTime);
begin
CheckVariable;
CheckResultType(rtDateTime);
FValue.ResDateTime := AValue;
end;
procedure TsExprIdentifierDef.SetAsFloat(const AValue: TsExprFloat);
begin
CheckVariable;
CheckResultType(rtFloat);
FValue.ResFloat := AValue;
end;
procedure TsExprIdentifierDef.SetAsInteger(const AValue: Int64);
begin
CheckVariable;
CheckResultType(rtInteger);
FValue.ResInteger := AValue;
end;
procedure TsExprIdentifierDef.SetAsString(const AValue: String);
begin
CheckVariable;
CheckResultType(rtString);
FValue.resString := AValue;
end;
procedure TsExprIdentifierDef.SetName(const AValue: ShortString);
begin
if FName = AValue then
exit;
if (AValue <> '') then
if Assigned(Collection) and (TsExprIdentifierDefs(Collection).IndexOfIdentifier(AValue) <> -1) then
RaiseParserError(SErrDuplicateIdentifier,[AValue]);
FName := AValue;
end;
procedure TsExprIdentifierDef.SetResultType(const AValue: TsResultType);
begin
if AValue <> FValue.ResultType then
begin
FValue.ResultType := AValue;
SetValue(FStringValue);
end;
end;
procedure TsExprIdentifierDef.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;
{------------------------------------------------------------------------------}
{ TsBuiltInExpressionManager }
{------------------------------------------------------------------------------}
constructor TsBuiltInExpressionManager.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDefs := TsExprIdentifierDefs.Create(TsBuiltInExprIdentifierDef)
end;
destructor TsBuiltInExpressionManager.Destroy;
begin
FreeAndNil(FDefs);
inherited Destroy;
end;
function TsBuiltInExpressionManager.AddVariable(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; AResultType: TsResultType; AValue: String
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.Addvariable(AName, AResultType, AValue));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddBooleanVariable(
const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: Boolean
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddBooleanvariable(AName, AValue));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddDateTimeVariable(
const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: TDateTime
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddDateTimeVariable(AName, AValue));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddFloatVariable(
const ACategory: TsBuiltInExprCategory; const AName: ShortString;
AValue: TsExprFloat): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddFloatVariable(AName, AValue));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddFunction(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; const AResultType: Char; const AParamTypes: String;
const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddFunction(AName, AResultType,
AParamTypes, AExcelCode, ACallBack));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddFunction(const ACategory: TsBuiltInExprCategory;
const AName: ShortString; const AResultType: Char; const AParamTypes: String;
const AExcelCode: Integer; ACallBack: TsExprFunctionEvent): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddFunction(AName, AResultType,
AParamTypes, AExcelCode, ACallBack));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddIntegerVariable(
const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: Integer
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddIntegerVariable(AName, AValue));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.AddStringVariable(
const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: String
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.AddStringVariable(AName, AValue));
Result.Category := ACategory;
end;
function TsBuiltInExpressionManager.FindIdentifier(const AName: ShortString
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.FindIdentifier(AName));
end;
function TsBuiltInExpressionManager.GetCount: Integer;
begin
Result := FDefs.Count;
end;
function TsBuiltInExpressionManager.GetI(AIndex: Integer): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs[Aindex])
end;
function TsBuiltInExpressionManager.IdentifierByExcelCode(const AExcelCode: Integer
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.IdentifierByExcelCode(AExcelCode));
end;
function TsBuiltInExpressionManager.IdentifierByName(const AName: ShortString
): TsBuiltInExprIdentifierDef;
begin
Result := TsBuiltInExprIdentifierDef(FDefs.IdentifierByName(AName));
end;
function TsBuiltInExpressionManager.IndexOfIdentifier(const AName: ShortString): Integer;
begin
Result := FDefs.IndexOfIdentifier(AName);
end;
{------------------------------------------------------------------------------}
{ Various Nodes }
{------------------------------------------------------------------------------}
{ TsExprNode }
procedure TsExprNode.CheckNodeType(ANode: TsExprNode; Allowed: TsResultTypes);
var
S: String;
A: TsResultType;
begin
if (ANode = nil) then
RaiseParserError(SErrNoNodeToCheck);
if not (ANode.NodeType in Allowed) then
begin
S := '';
for A := Low(TsResultType) to High(TsResultType) 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 TsExprNode.NodeValue: TsExpressionResult;
begin
GetNodeValue(Result);
end;
{ TsUnaryOperationExprNode }
constructor TsUnaryOperationExprNode.Create(AOperand: TsExprNode);
begin
FOperand := AOperand;
end;
destructor TsUnaryOperationExprNode.Destroy;
begin
FreeAndNil(FOperand);
inherited Destroy;
end;
procedure TsUnaryOperationExprNode.Check;
begin
if not Assigned(Operand) then
RaiseParserError(SErrNoOperand, [Self.ClassName]);
end;
{ TsBinaryOperationExprNode }
constructor TsBinaryOperationExprNode.Create(ALeft, ARight: TsExprNode);
begin
FLeft := ALeft;
FRight := ARight;
end;
destructor TsBinaryOperationExprNode.Destroy;
begin
FreeAndNil(FLeft);
FreeAndNil(FRight);
inherited Destroy;
end;
procedure TsBinaryOperationExprNode.Check;
begin
if not Assigned(Left) then
RaiseParserError(SErrNoLeftOperand,[classname]);
if not Assigned(Right) then
RaiseParserError(SErrNoRightOperand,[classname]);
end;
procedure TsBinaryOperationExprNode.CheckSameNodeTypes;
var
LT, RT: TsResultType;
begin
LT := Left.NodeType;
RT := Right.NodeType;
if (RT <> LT) then
RaiseParserError(SErrTypesDoNotMatch, [ResultTypeName(LT), ResultTypeName(RT), Left.AsString, Right.AsString])
end;
{ TsBooleanOperationExprNode }
procedure TsBooleanOperationExprNode.Check;
begin
inherited Check;
CheckNodeType(Left, [rtBoolean, rtCell, rtError, rtEmpty]);
CheckNodeType(Right, [rtBoolean, rtCell, rtError, rtEmpty]);
CheckSameNodeTypes;
end;
function TsBooleanOperationExprNode.NodeType: TsResultType;
begin
Result := Left.NodeType;
end;
{ TsConstExprNode }
constructor TsConstExprNode.CreateString(AValue: String);
begin
FValue.ResultType := rtString;
FValue.ResString := AValue;
end;
constructor TsConstExprNode.CreateInteger(AValue: Int64);
begin
FValue.ResultType := rtInteger;
FValue.ResInteger := AValue;
end;
constructor TsConstExprNode.CreateDateTime(AValue: TDateTime);
begin
FValue.ResultType := rtDateTime;
FValue.ResDateTime := AValue;
end;
constructor TsConstExprNode.CreateFloat(AValue: TsExprFloat);
begin
Inherited Create;
FValue.ResultType := rtFloat;
FValue.ResFloat := AValue;
end;
constructor TsConstExprNode.CreateBoolean(AValue: Boolean);
begin
FValue.ResultType := rtBoolean;
FValue.ResBoolean := AValue;
end;
constructor TsConstExprNode.CreateError(AValue: TsErrorValue);
begin
FValue.ResultType := rtError;
FValue.ResError := AValue;
end;
procedure TsConstExprNode.Check;
begin
// Nothing to check;
end;
function TsConstExprNode.NodeType: TsResultType;
begin
Result := FValue.ResultType;
end;
procedure TsConstExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Result := FValue;
end;
function TsConstExprNode.AsString: string;
begin
case NodeType of
rtString : Result := cDoubleQuote + FValue.ResString + cDoubleQuote;
rtInteger : Result := IntToStr(FValue.ResInteger);
rtDateTime : Result := '''' + FormatDateTime('cccc', FValue.ResDateTime) + ''''; // Probably wrong !!!
rtBoolean : if FValue.ResBoolean then Result := 'TRUE' else Result := 'FALSE';
rtFloat : Result := FloatToStr(FValue.ResFloat, ExprFormatSettings);
end;
end;
function TsConstExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
case NodeType of
rtString : Result := RPNString(FValue.ResString, ANext);
rtInteger : Result := RPNInteger(FValue.ResInteger, ANext);
rtDateTime : Result := RPNNumber(FValue.ResDateTime, ANext);
rtBoolean : Result := RPNBool(FValue.ResBoolean, ANext);
rtFloat : Result := RPNNumber(FValue.ResFloat, ANext);
end;
end;
{ TsUPlusExprNode }
function TsUPlusExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekUPlus,
Operand.AsRPNItem(
ANext
));
end;
function TsUPlusExprNode.AsString: String;
begin
Result := '+' + TrimLeft(Operand.AsString);
end;
procedure TsUPlusExprNode.Check;
const
AllowedTokens = [rtInteger, rtFloat, rtCell, rtEmpty, rtError];
begin
inherited;
if not (Operand.NodeType in AllowedTokens) then
RaiseParserError(SErrNoUPlus, [ResultTypeName(Operand.NodeType), Operand.AsString])
end;
procedure TsUPlusExprNode.GetNodeValue(var Result: TsExpressionResult);
var
res: TsExpressionresult;
cell: PCell;
begin
Operand.GetNodeValue(Result);
case Result.ResultType of
rtInteger, rtFloat, rtError:
exit;
rtCell:
begin
cell := ArgToCell(Result);
if cell = nil then
Result := FloatResult(0.0)
else
if cell^.ContentType = cctNumber then
begin
if frac(cell^.NumberValue) = 0.0 then
Result := IntegerResult(trunc(cell^.NumberValue))
else
Result := FloatResult(cell^.NumberValue);
end;
end;
rtEmpty:
Result := FloatResult(0.0);
else
Result := ErrorResult(errWrongType);
end;
end;
function TsUPlusExprNode.NodeType: TsResultType;
begin
Result := Operand.NodeType;
end;
{ TsUMinusExprNode }
function TsUMinusExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekUMinus,
Operand.AsRPNItem(
ANext
));
end;
function TsUMinusExprNode.AsString: String;
begin
Result := '-' + TrimLeft(Operand.AsString);
end;
procedure TsUMinusExprNode.Check;
const
AllowedTokens = [rtInteger, rtFloat, rtCell, rtEmpty, rtError];
begin
inherited;
if not (Operand.NodeType in AllowedTokens) then
RaiseParserError(SErrNoNegation, [ResultTypeName(Operand.NodeType), Operand.AsString])
end;
procedure TsUMinusExprNode.GetNodeValue(var Result: TsExpressionResult);
var
cell: PCell;
begin
Operand.GetNodeValue(Result);
case Result.ResultType of
rtError:
exit;
rtFloat:
Result := FloatResult(-Result.ResFloat);
rtInteger:
Result := IntegerResult(-Result.ResInteger);
rtCell:
begin
cell := ArgToCell(Result);
if (cell <> nil) and (cell^.ContentType = cctNumber) then
begin
if frac(cell^.NumberValue) = 0.0 then
Result := IntegerResult(-trunc(cell^.NumberValue))
else
Result := FloatResult(cell^.NumberValue);
end else
Result := FloatResult(0.0);
end;
rtEmpty:
Result := FloatResult(0.0);
else
Result := ErrorResult(errWrongType);
end;
end;
function TsUMinusExprNode.NodeType: TsResultType;
begin
Result := Operand.NodeType;
end;
{ TsPercentExprNode }
function TsPercentExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekPercent,
Operand.AsRPNItem(
ANext
));
end;
function TsPercentExprNode.AsString: String;
begin
Result := Operand.AsString + '%';
end;
procedure TsPercentExprNode.Check;
const
AllowedTokens = [rtInteger, rtFloat, rtCell, rtEmpty, rtError];
begin
inherited;
if not (Operand.NodeType in AllowedTokens) then
RaiseParserError(SErrNoPercentOperation, [ResultTypeName(Operand.NodeType), Operand.AsString])
end;
procedure TsPercentExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Operand.GetNodeValue(Result);
case Result.ResultType of
rtError:
exit;
rtFloat, rtInteger, rtCell:
Result := FloatResult(ArgToFloat(Result)*0.01);
else
Result := ErrorResult(errWrongType);
end;
end;
function TsPercentExprNode.NodeType: TsResultType;
begin
Result := rtFloat;
end;
{ TsParenthesisExprNode }
function TsParenthesisExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekParen,
Operand.AsRPNItem(
ANext
));
end;
function TsParenthesisExprNode.AsString: String;
begin
Result := '(' + Operand.AsString + ')';
end;
function TsParenthesisExprNode.NodeType: TsResultType;
begin
Result := Operand.NodeType;
end;
procedure TsParenthesisExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Result := Operand.NodeValue;
end;
{ TsNotExprNode }
function TsNotExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc('NOT',
Operand.AsRPNItem(
ANext
));
end;
function TsNotExprNode.AsString: String;
begin
Result := 'not ' + Operand.AsString;
end;
procedure TsNotExprNode.Check;
const
AllowedTokens = [rtBoolean, rtEmpty, rtError];
begin
if not (Operand.NodeType in AllowedTokens) then
RaiseParserError(SErrNoNotOperation, [ResultTypeName(Operand.NodeType), Operand.AsString])
end;
procedure TsNotExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Operand.GetNodeValue(Result);
case Result.ResultType of
rtBoolean : Result.ResBoolean := not Result.ResBoolean;
rtEmpty : Result := BooleanResult(true);
end
end;
function TsNotExprNode.NodeType: TsResultType;
begin
Result := Operand.NodeType;
end;
{ TsBooleanResultExprNode }
procedure TsBooleanResultExprNode.Check;
begin
inherited Check;
CheckSameNodeTypes;
end;
procedure TsBooleanResultExprNode.CheckSameNodeTypes;
begin
// Same node types are checked in GetNodevalue
end;
function TsBooleanResultExprNode.NodeType: TsResultType;
begin
Result := rtBoolean;
end;
{ TsEqualExprNode }
function TsEqualExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekEqual,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsEqualExprNode.AsString: string;
begin
Result := Left.AsString + '=' + Right.AsString;
end;
procedure TsEqualExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
begin
Left.GetNodeValue(Result);
Right.GetNodeValue(RRes);
if (Result.ResultType in [rtInteger, rtFloat, rtCell, rtEmpty]) and
(RRes.ResultType in [rtInteger, rtFloat, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToFloat(Result) = ArgToFloat(RRes))
else
if (Result.ResultType in [rtString, rtCell, rtEmpty]) and
(RRes.ResultType in [rtString, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToString(Result) = ArgToString(RRes))
else
if (Result.ResultType in [rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtDateTime, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToDateTime(Result) = ArgToDateTime(RRes))
else
if (Result.ResultType in [rtBoolean, rtCell, rtEmpty]) and
(RRes.ResultType in [rtBoolean, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToBoolean(Result) = ArgToBoolean(RRes))
else
if (Result.ResultType = rtError)
then Result := ErrorResult(Result.ResError)
else
if (RRes.ResultType = rtError)
then Result := ErrorResult(RRes.ResError)
else
Result := BooleanResult(false);
end;
{ TsNotEqualExprNode }
function TsNotEqualExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekNotEqual,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsNotEqualExprNode.AsString: string;
begin
Result := Left.AsString + '<>' + Right.AsString;
end;
procedure TsNotEqualExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
inherited GetNodeValue(Result);
Result.ResBoolean := not Result.ResBoolean;
end;
{ TsOrderingExprNode }
procedure TsOrderingExprNode.Check;
const
AllowedTypes = [rtBoolean, rtInteger, rtFloat, rtDateTime, rtString, rtEmpty, rtError, rtCell];
begin
CheckNodeType(Left, AllowedTypes);
CheckNodeType(Right, AllowedTypes);
inherited Check;
end;
procedure TsOrderingExprNode.CheckSameNodeTypes;
var
LT, RT: TsResultType;
begin
{
LT := Left.NodeType;
RT := Right.NodeType;
case LT of
rtFloat, rtInteger:
if (RT in [rtFloat, rtInteger]) or
((Rt = rtCell) and (Right.Res
if (RT <> LT) then
RaiseParserError(SErrTypesDoNotMatch, [ResultTypeName(LT), ResultTypeName(RT), Left.AsString, Right.AsString])
}
end;
{ TsLessExprNode }
function TsLessExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekLess,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsLessExprNode.AsString: string;
begin
Result := Left.AsString + '<' + Right.AsString;
end;
procedure TsLessExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
begin
Left.GetNodeValue(Result);
Right.GetNodeValue(RRes);
if (Result.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToFloat(Result) < ArgToFloat(RRes))
else
if (Result.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty]) and
(RRes.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToString(Result) < ArgToString(RRes))
else
if (Result.ResultType in [rtBoolean, rtCell, rtEmpty]) and
(RRes.ResultType in [rtBoolean, rtCell, rtEmpty])
then
Result := BooleanResult(ord(ArgToBoolean(Result)) < ord(ArgToBoolean(RRes)))
else
if (Result.ResultType = rtError)
then Result := ErrorResult(Result.ResError)
else
if (RRes.ResultType = rtError)
then Result := ErrorResult(RRes.ResError)
else
Result := ErrorResult(errWrongType);
end;
{ TsGreaterExprNode }
function TsGreaterExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekGreater,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsGreaterExprNode.AsString: string;
begin
Result := Left.AsString + '>' + Right.AsString;
end;
procedure TsGreaterExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
begin
Left.GetNodeValue(Result);
Right.GetNodeValue(RRes);
if (Result.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToFloat(Result) > ArgToFloat(RRes))
else
if (Result.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty]) and
(RRes.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty])
then
Result := BooleanResult(ArgToString(Result) > ArgToString(RRes))
else
if (Result.ResultType in [rtBoolean, rtCell, rtEmpty]) and
(RRes.ResultType in [rtBoolean, rtCell, rtEmpty])
then
Result := BooleanResult(ord(ArgToBoolean(Result)) > ord(ArgToBoolean(RRes)))
else
if (Result.ResultType = rtError)
then Result := ErrorResult(Result.ResError)
else
if (RRes.ResultType = rtError)
then Result := ErrorResult(RRes.ResError)
else
Result := ErrorResult(errWrongType);
end;
{ TsGreaterEqualExprNode }
function TsGreaterEqualExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekGreaterEqual,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsGreaterEqualExprNode.AsString: string;
begin
Result := Left.AsString + '>=' + Right.AsString;
end;
procedure TsGreaterEqualExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
inherited GetNodeValue(Result);
Result.ResBoolean := not Result.ResBoolean;
end;
{ TsLessEqualExprNode }
function TsLessEqualExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekLessEqual,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsLessEqualExprNode.AsString: string;
begin
Result := Left.AsString + '<=' + Right.AsString;
end;
procedure TsLessEqualExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
inherited GetNodeValue(Result);
Result.ResBoolean := not Result.ResBoolean;
end;
{ TsConcatExprNode }
function TsConcatExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekConcat,
Right.AsRPNItem(
Left.AsRPNItem(
nil)));
end;
function TsConcatExprNode.AsString: string;
begin
Result := Left.AsString + '&' + Right.AsString;
end;
procedure TsConcatExprNode.Check;
begin
inherited Check;
CheckNodeType(Left, [rtString, rtCell, rtEmpty, rtError]);
CheckNodeType(Right, [rtString, rtCell, rtEmpty, rtError]);
end;
procedure TsConcatExprNode.CheckSameNodeTypes;
begin
// Same node types are checked in GetNodevalue
end;
procedure TsConcatExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes : TsExpressionResult;
begin
Left.GetNodeValue(Result);
if (Result.ResultType = rtError)
then exit;
Right.GetNodeValue(RRes);
if (Result.ResultType in [rtString, rtCell]) and (RRes.ResultType in [rtString, rtCell])
then Result := StringResult(ArgToString(Result) + ArgToString(RRes))
else
if (RRes.ResultType = rtError)
then Result := ErrorResult(RRes.ResError)
else
Result := ErrorResult(errWrongType);
end;
function TsConcatExprNode.NodeType: TsResultType;
begin
Result := rtString;
end;
{ TsMathOperationExprNode }
procedure TsMathOperationExprNode.Check;
const
AllowedTypes = [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty, rtError];
begin
inherited Check;
CheckNodeType(Left, AllowedTypes);
CheckNodeType(Right, AllowedTypes);
CheckSameNodeTypes;
end;
procedure TsMathOperationExprNode.CheckSameNodeTypes;
begin
// Same node types are checked in GetNodevalue
end;
function TsMathOperationExprNode.NodeType: TsResultType;
begin
Result := Left.NodeType;
end;
{ TsAddExprNode }
function TsAddExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekAdd,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsAddExprNode.AsString: string;
begin
Result := Left.AsString + '+' + Right.AsString;
end;
procedure TsAddExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
begin
Left.GetNodeValue(Result);
if Result.ResultType = rtError then
exit;
Right.GetNodeValue(RRes);
if RRes.ResultType = rtError then
begin
Result := ErrorResult(RRes.ResError);
exit;
end;
if (Result.ResultType in [rtInteger, rtCell, rtEmpty]) and
(RRes.ResultType in [rtInteger, rtCell, rtEmpty])
then
Result := IntegerResult(ArgToInt(Result) + ArgToInt(RRes))
else
if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty])
then
Result := FloatResult(ArgToFloat(Result) + ArgToFloat(RRes));
end;
{ TsSubtractExprNode }
function TsSubtractExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekSub,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsSubtractExprNode.AsString: string;
begin
Result := Left.AsString + '-' + Right.asString;
end;
procedure TsSubtractExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
begin
Left.GetNodeValue(Result);
if Result.ResultType = rtError then
exit;
Right.GetNodeValue(RRes);
if RRes.ResultType = rtError then
begin
Result := ErrorResult(RRes.ResError);
exit;
end;
if (Result.ResultType in [rtInteger, rtCell, rtEmpty]) and
(RRes.ResultType in [rtInteger, rtCell, rtEmpty])
then
Result := IntegerResult(ArgToInt(Result) - ArgToInt(RRes))
else
if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty])
then
Result := FloatResult(ArgToFloat(Result) - ArgToFloat(RRes));
end;
{ TsMultiplyExprNode }
function TsMultiplyExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekMul,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsMultiplyExprNode.AsString: string;
begin
Result := Left.AsString + '*' + Right.AsString;
end;
procedure TsMultiplyExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
begin
Left.GetNodeValue(Result);
if Result.ResultType = rtError then
exit;
Right.GetNodeValue(RRes);
if RRes.ResultType = rtError then
begin
Result := ErrorResult(RRes.ResError);
exit;
end;
if (Result.ResultType in [rtInteger, rtCell, rtEmpty]) and
(RRes.ResultType in [rtInteger, rtCell, rtEmpty])
then
Result := IntegerResult(ArgToInt(Result) * ArgToInt(RRes))
else
if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty])
then
Result := FloatResult(ArgToFloat(Result) * ArgToFloat(RRes));
end;
{ TsDivideExprNode }
function TsDivideExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekDiv,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsDivideExprNode.AsString: string;
begin
Result := Left.AsString + '/' + Right.asString;
end;
procedure TsDivideExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
y: TsExprFloat;
begin
Left.GetNodeValue(Result);
if Result.ResultType = rtError then
exit;
Right.GetNodeValue(RRes);
if RRes.ResultType = rtError then
begin
Result := ErrorResult(RRes.ResError);
exit;
end;
if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty])
then begin
y := ArgToFloat(RRes);
if y = 0.0 then
Result := ErrorResult(errDivideByZero)
else
Result := FloatResult(ArgToFloat(Result) / y);
end;
end;
function TsDivideExprNode.NodeType: TsResultType;
begin
Result := rtFLoat;
end;
{ TsPowerExprNode }
function TsPowerExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := RPNFunc(fekPower,
Right.AsRPNItem(
Left.AsRPNItem(
ANext
)));
end;
function TsPowerExprNode.AsString: string;
begin
Result := Left.AsString + '^' + Right.AsString;
end;
procedure TsPowerExprNode.GetNodeValue(var Result: TsExpressionResult);
var
RRes: TsExpressionResult;
ex: TsExprFloat;
begin
Left.GetNodeValue(Result);
if Result.ResultType = rtError then
exit;
Right.GetNodeValue(RRes);
if RRes.ResultType = rtError then
begin
Result := ErrorResult(RRes.ResError);
exit;
end;
if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and
(RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty])
then
try
Result := FloatResult(Power(ArgToFloat(Result), ArgToFloat(RRes)));
except
on E: EInvalidArgument do Result := ErrorResult(errOverflow);
end;
end;
function TsPowerExprNode.NodeType: TsResultType;
begin
Result := rtFloat;
end;
{ TsConvertExprNode }
function TsConvertExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
Result := Operand.AsRPNItem(ANext);
end;
function TsConvertExprNode.AsString: String;
begin
Result := Operand.AsString;
end;
{ TsIntToFloatExprNode }
procedure TsConvertToIntExprNode.Check;
begin
inherited Check;
CheckNodeType(Operand, [rtInteger, rtCell])
end;
procedure TsIntToFloatExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Operand.GetNodeValue(Result);
if Result.ResultType in [rtInteger, rtCell] then
Result := FloatResult(ArgToInt(Result));
end;
function TsIntToFloatExprNode.NodeType: TsResultType;
begin
Result := rtFloat;
end;
{ TsIntToDateTimeExprNode }
function TsIntToDateTimeExprNode.NodeType: TsResultType;
begin
Result := rtDatetime;
end;
procedure TsIntToDateTimeExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Operand.GetnodeValue(Result);
if Result.ResultType in [rtInteger, rtCell] then
Result := DateTimeResult(ArgToInt(Result));
end;
{ TsFloatToDateTimeExprNode }
procedure TsFloatToDateTimeExprNode.Check;
begin
inherited Check;
CheckNodeType(Operand, [rtFloat, rtCell]);
end;
function TsFloatToDateTimeExprNode.NodeType: TsResultType;
begin
Result := rtDateTime;
end;
procedure TsFloatToDateTimeExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Operand.GetNodeValue(Result);
if Result.ResultType in [rtFloat, rtCell] then
Result := DateTimeResult(ArgToFloat(Result));
end;
{ TsIdentifierExprNode }
constructor TsIdentifierExprNode.CreateIdentifier(AID: TsExprIdentifierDef);
begin
inherited Create;
FID := AID;
PResult := @FID.FValue;
FResultType := FID.ResultType;
end;
function TsIdentifierExprNode.NodeType: TsResultType;
begin
Result := FResultType;
end;
procedure TsIdentifierExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Result := PResult^;
Result.ResultType := FResultType;
end;
{ TsVariableExprNode }
procedure TsVariableExprNode.Check;
begin
// Do nothing;
end;
function TsVariableExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
RaiseParserError('Cannot handle variables for RPN, so far.');
end;
function TsVariableExprNode.AsString: string;
begin
Result := FID.Name;
end;
{ TsFunctionExprNode }
constructor TsFunctionExprNode.CreateFunction(AID: TsExprIdentifierDef;
const Args: TsExprArgumentArray);
begin
inherited CreateIdentifier(AID);
FArgumentNodes := Args;
SetLength(FArgumentParams, Length(Args));
end;
destructor TsFunctionExprNode.Destroy;
var
i: Integer;
begin
for i:=0 to Length(FArgumentNodes)-1 do
FreeAndNil(FArgumentNodes[i]);
inherited Destroy;
end;
function TsFunctionExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
var
i, n: Integer;
begin
if FID.HasFixedArgumentCount then
n := FID.ArgumentCount
else
n := Length(FArgumentNodes);
Result := ANext;
// for i:=Length(FArgumentNodes)-1 downto 0 do
for i:=0 to High(FArgumentNodes) do
Result := FArgumentNodes[i].AsRPNItem(Result);
Result := RPNFunc(FID.Name, n, Result);
end;
function TsFunctionExprNode.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;
S := '(' + S + ')';
Result := FID.Name + S;
end;
procedure TsFunctionExprNode.CalcParams;
var
i : Integer;
begin
for i := 0 to Length(FArgumentParams)-1 do
{
case FArgumentParams[i].ResultType of
rtEmpty: FID.FValue.ResultType := rtEmpty;
rtError: if FID.FValue.ResultType <> rtError then
begin
FID.FValue.ResultType := rtError;
FID.FValue.ResError := FArgumentParams[i].ResError;
end;
else FArgumentNodes[i].GetNodeValue(FArgumentParams[i]);
end;
}
FArgumentNodes[i].GetNodeValue(FArgumentParams[i]);
end;
procedure TsFunctionExprNode.Check;
var
i: Integer;
rta, // parameter types passed to the function
rtp: TsResultType; // Parameter types expected from the parameter symbol
lastrtp: TsResultType;
begin
if Length(FArgumentNodes) <> FID.ArgumentCount then
begin
for i:=Length(FArgumentNodes)+1 to FID.ArgumentCount do
if not FID.IsOptionalArgument(i) then
RaiseParserError(ErrInvalidArgumentCount, [FID.Name]);
end;
for i := 0 to Length(FArgumentNodes)-1 do
begin
rta := FArgumentNodes[i].NodeType;
// A "cell" can return any type --> no type conversion required here.
if rta = rtCell then
Continue;
if i+1 <= Length(FID.ParameterTypes) then
begin
rtp := CharToResultType(FID.ParameterTypes[i+1]);
lastrtp := rtp;
end else
rtp := lastrtp;
if rtp = rtAny then
Continue;
if (rtp <> rta) and not (rta in [rtCellRange, rtError, rtEmpty]) then
begin
// Automatically convert integers to floats in functions that return a float
if (rta = rtInteger) and (rtp = rtFloat) then
begin
FArgumentNodes[i] := TsIntToFloatExprNode(FArgumentNodes[i]);
exit;
end;
// Floats are truncated automatically to integers - that's what Excel does.
if (rta = rtFloat) and (rtp = rtInteger) then
exit;
RaiseParserError(SErrInvalidArgumentType, [i+1, ResultTypeName(rtp), ResultTypeName(rta)])
end;
end;
end;
{ TsFunctionCallBackExprNode }
constructor TsFunctionCallBackExprNode.CreateFunction(AID: TsExprIdentifierDef;
const Args: TsExprArgumentArray);
begin
inherited;
FCallBack := AID.OnGetFunctionValueCallBack;
end;
procedure TsFunctionCallBackExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Result.ResultType := NodeType; // was at end!
if Length(FArgumentParams) > 0 then
CalcParams;
FCallBack(Result, FArgumentParams);
end;
{ TFPFunctionEventHandlerExprNode }
constructor TFPFunctionEventHandlerExprNode.CreateFunction(AID: TsExprIdentifierDef;
const Args: TsExprArgumentArray);
begin
inherited;
FCallBack := AID.OnGetFunctionValue;
end;
procedure TFPFunctionEventHandlerExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
Result.ResultType := NodeType; // was at end
if Length(FArgumentParams) > 0 then
CalcParams;
FCallBack(Result, FArgumentParams);
end;
{ TsCellExprNode }
constructor TsCellExprNode.Create(AWorksheet: TsWorksheet; ACellString: String);
var
r, c: Cardinal;
flags: TsRelFlags;
begin
ParseCellString(ACellString, r, c, flags);
Create(AWorksheet, r, c, flags);
end;
constructor TsCellExprNode.Create(AWorksheet: TsWorksheet; ARow,ACol: Cardinal;
AFlags: TsRelFlags);
begin
FWorksheet := AWorksheet;
FRow := ARow;
FCol := ACol;
FFlags := AFlags;
FCell := AWorksheet.FindCell(FRow, FCol);
end;
function TsCellExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
if FIsRef then
Result := RPNCellRef(FRow, FCol, FFlags, ANext)
else
Result := RPNCellValue(FRow, FCol, FFlags, ANext);
end;
function TsCellExprNode.AsString: string;
begin
Result := GetCellString(FRow, FCol, FFlags);
end;
procedure TsCellExprNode.GetNodeValue(var Result: TsExpressionResult);
begin
if (FCell <> nil) and HasFormula(FCell) then
case FCell^.CalcState of
csNotCalculated:
Worksheet.CalcFormula(FCell);
csCalculating:
raise Exception.Create(SErrCircularReference);
end;
Result.ResultType := rtCell;
Result.ResRow := FRow;
Result.ResCol := FCol;
Result.Worksheet := FWorksheet;
end;
procedure TsCellExprNode.Check;
begin
// Nothing to check;
end;
function TsCellExprNode.NodeType: TsResultType;
begin
Result := rtCell;
{
if FIsRef then
Result := rtCell
else
begin
Result := rtEmpty;
if FCell <> nil then
case FCell^.ContentType of
cctNumber:
if frac(FCell^.NumberValue) = 0 then
Result := rtInteger
else
Result := rtFloat;
cctDateTime:
Result := rtDateTime;
cctUTF8String:
Result := rtString;
cctBool:
Result := rtBoolean;
cctError:
Result := rtError;
end;
end;
}
end;
{ TsCellRangeExprNode }
constructor TsCellRangeExprNode.Create(AWorksheet: TsWorksheet; ACellRangeString: String);
var
r1, c1, r2, c2: Cardinal;
flags: TsRelFlags;
begin
if pos(':', ACellRangeString) = 0 then
begin
ParseCellString(ACellRangeString, r1, c1, flags);
if rfRelRow in flags then Include(flags, rfRelRow2);
if rfRelCol in flags then Include(flags, rfRelCol2);
Create(AWorksheet, r1, c1, r1, c1, flags);
end else
begin
ParseCellRangeString(ACellRangeString, r1, c1, r2, c2, flags);
Create(AWorksheet, r1, c1, r2, c2, flags);
end;
end;
constructor TsCellRangeExprNode.Create(AWorksheet: TsWorksheet;
ARow1,ACol1,ARow2,ACol2: Cardinal; AFlags: TsRelFlags);
begin
FWorksheet := AWorksheet;
FRow1 := ARow1;
FCol1 := ACol1;
FRow2 := ARow2;
FCol2 := ACol2;
FFlags := AFlags;
end;
function TsCellRangeExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
{
if (FRow1 = FRow2) and (FCol1 = FCol2) then
Result := RPNCellRef(FRow1, FCol1, FFlags, ANext)
else
}
Result := RPNCellRange(FRow1, FCol1, FRow2, FCol2, FFlags, ANext);
end;
function TsCellRangeExprNode.AsString: string;
begin
if (FRow1 = FRow2) and (FCol1 = FCol2) then
Result := GetCellString(FRow1, FCol1, FFlags)
else
Result := GetCellRangeString(FRow1, FCol1, FRow2, FCol2, FFlags);
end;
procedure TsCellRangeExprNode.Check;
begin
// Nothing to check;
end;
procedure TsCellRangeExprNode.GetNodeValue(var Result: TsExpressionResult);
var
r,c: Cardinal;
cell: PCell;
begin
for r := FRow1 to FRow2 do
for c := FCol1 to FCol2 do
begin
cell := FWorksheet.FindCell(r, c);
if HasFormula(cell) then
case cell^.CalcState of
csNotCalculated: FWorksheet.CalcFormula(cell);
csCalculating : raise Exception.Create(SErrCircularReference);
end;
end;
Result.ResultType := rtCellRange;
Result.ResCellRange.Row1 := FRow1;
Result.ResCellRange.Col1 := FCol1;
Result.ResCellRange.Row2 := FRow2;
Result.ResCellRange.Col2 := FCol2;
Result.Worksheet := FWorksheet;
end;
function TsCellRangeExprNode.NodeType: TsResultType;
begin
Result := rtCellRange;
end;
{------------------------------------------------------------------------------}
{ Conversion of arguments to simple data types }
{------------------------------------------------------------------------------}
function ArgToBoolean(Arg: TsExpressionResult): Boolean;
var
cell: PCell;
begin
Result := false;
if Arg.ResultType = rtBoolean then
Result := Arg.ResBoolean
else
if (Arg.ResultType = rtCell) then begin
cell := ArgToCell(Arg);
if (cell <> nil) and (cell^.ContentType = cctBool) then
Result := cell^.BoolValue;
end;
end;
function ArgToCell(Arg: TsExpressionResult): PCell;
begin
if Arg.ResultType = rtCell then
Result := Arg.Worksheet.FindCell(Arg.ResRow, Arg.ResCol)
else
Result := nil;
end;
function ArgToInt(Arg: TsExpressionResult): Integer;
var
cell: PCell;
begin
Result := 0;
if Arg.ResultType = rtInteger then
result := Arg.ResInteger
else
if Arg.ResultType = rtFloat then
result := trunc(Arg.ResFloat)
else
if Arg.ResultType = rtDateTime then
result := trunc(Arg.ResDateTime)
else
if (Arg.ResultType = rtCell) then
begin
cell := ArgToCell(Arg);
if Assigned(cell) and (cell^.ContentType = cctNumber) then
result := trunc(cell^.NumberValue);
end;
end;
function ArgToFloat(Arg: TsExpressionResult): TsExprFloat;
// Utility function for the built-in math functions. Accepts also integers
// in place of the floating point arguments. To be called in builtins or
// user-defined callbacks having float results.
var
cell: PCell;
begin
Result := 0.0;
if Arg.ResultType = rtInteger then
result := Arg.ResInteger
else
if Arg.ResultType = rtDateTime then
result := Arg.ResDateTime
else
if Arg.ResultType = rtFloat then
result := Arg.ResFloat
else
if (Arg.ResultType = rtCell) then
begin
cell := ArgToCell(Arg);
if Assigned(cell) then
case cell^.ContentType of
cctNumber : Result := cell^.NumberValue;
cctDateTime : Result := cell^.DateTimeValue;
end;
end;
end;
function ArgToDateTime(Arg: TsExpressionResult): TDateTime;
var
cell: PCell;
begin
Result := 0.0;
if Arg.ResultType = rtDateTime then
result := Arg.ResDateTime
else
if Arg.ResultType = rtInteger then
Result := Arg.ResInteger
else
if Arg.ResultType = rtFloat then
Result := Arg.ResFloat
else
if (Arg.ResultType = rtCell) then
begin
cell := ArgToCell(Arg);
if Assigned(cell) and (cell^.ContentType = cctDateTime) then
Result := cell^.DateTimeValue;
end;
end;
function ArgToString(Arg: TsExpressionResult): String;
var
cell: PCell;
begin
Result := '';
case Arg.ResultType of
rtString : result := Arg.ResString;
rtInteger : Result := IntToStr(Arg.ResInteger);
rtFloat : Result := FloatToStr(Arg.ResFloat);
rtCell : begin
cell := ArgToCell(Arg);
if Assigned(cell) and (cell^.ContentType = cctUTF8String) then
Result := cell^.UTF8Stringvalue;
end;
end;
end;
{------------------------------------------------------------------------------}
{ Conversion simple data types to ExpressionResults }
{------------------------------------------------------------------------------}
function BooleanResult(AValue: Boolean): TsExpressionResult;
begin
Result.ResultType := rtBoolean;
Result.ResBoolean := AValue;
end;
function DateTimeResult(AValue: TDateTime): TsExpressionResult;
begin
Result.ResultType := rtDateTime;
Result.ResDateTime := AValue;
end;
function EmptyResult: TsExpressionResult;
begin
Result.ResultType := rtEmpty;
end;
function ErrorResult(const AValue: TsErrorValue): TsExpressionResult;
begin
Result.ResultType := rtError;
Result.ResError := AValue;
end;
function FloatResult(const AValue: TsExprFloat): TsExpressionResult;
begin
Result.ResultType := rtFloat;
Result.ResFloat := AValue;
end;
function IntegerResult(const AValue: Integer): TsExpressionResult;
begin
Result.ResultType := rtInteger;
Result.ResInteger := AValue;
end;
function StringResult(const AValue: string): TsExpressionResult;
begin
Result.ResultType := rtString;
Result.ResString := AValue;
end;
{------------------------------------------------------------------------------}
{ Standard Builtins support }
{------------------------------------------------------------------------------}
// Builtin math functions
procedure fpsABS(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(abs(ArgToFloat(Args[0])));
end;
procedure fpsACOS(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if InRange(x, -1, +1) then
Result := FloatResult(arccos(x))
else
Result := ErrorResult(errOverflow); // #NUM!
end;
procedure fpsACOSH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if x >= 1 then
Result := FloatResult(arccosh(ArgToFloat(Args[0])))
else
Result := ErrorResult(errOverflow);
end;
procedure fpsASIN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if InRange(x, -1, +1) then
Result := FloatResult(arcsin(ArgToFloat(Args[0])))
else
Result := ErrorResult(errOverflow);
end;
procedure fpsASINH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(arcsinh(ArgToFloat(Args[0])));
end;
procedure fpsATAN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(arctan(ArgToFloat(Args[0])));
end;
procedure fpsATANH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if (x > -1) and (x < +1) then
Result := FloatResult(arctanh(ArgToFloat(Args[0])))
else
Result := ErrorResult(errOverflow); // #NUM!
end;
procedure fpsCOS(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(cos(ArgToFloat(Args[0])));
end;
procedure fpsCOSH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(cosh(ArgToFloat(Args[0])));
end;
procedure fpsDEGREES(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(RadToDeg(ArgToFloat(Args[0])));
end;
procedure fpsEXP(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(exp(ArgToFloat(Args[0])));
end;
procedure fpsINT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(floor(ArgToFloat(Args[0])));
end;
procedure fpsLN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if x > 0 then
Result := FloatResult(ln(x))
else
Result := ErrorResult(errOverflow); // #NUM!
end;
procedure fpsLOG(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// LOG( number [, base] ) - base is 10 if omitted.
var
x: TsExprFloat;
base: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if x <= 0 then begin
Result := ErrorResult(errOverflow); // #NUM!
exit;
end;
if Length(Args) = 2 then begin
base := ArgToFloat(Args[1]);
if base < 0 then begin
Result := ErrorResult(errOverflow); // #NUM!
exit;
end;
end else
base := 10;
Result := FloatResult(logn(base, x));
end;
procedure fpsLOG10(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if x > 0 then
Result := FloatResult(log10(x))
else
Result := ErrorResult(errOverflow); // #NUM!
end;
procedure fpsPI(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Unused(Args);
Result := FloatResult(pi);
end;
procedure fpsPOWER(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
try
Result := FloatResult(Power(ArgToFloat(Args[0]), ArgToFloat(Args[1])));
except
Result := ErrorResult(errOverflow);
end;
end;
procedure fpsRADIANS(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(DegToRad(ArgToFloat(Args[0])));
end;
procedure fpsRAND(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Unused(Args);
Result := FloatResult(random);
end;
procedure fpsROUND(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
n: Integer;
begin
if Args[1].ResultType = rtInteger then
n := Args[1].ResInteger
else
n := round(Args[1].ResFloat);
Result := FloatResult(RoundTo(ArgToFloat(Args[0]), n));
end;
procedure fpsSIGN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(sign(ArgToFloat(Args[0])));
end;
procedure fpsSIN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(sin(ArgToFloat(Args[0])));
end;
procedure fpsSINH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(sinh(ArgToFloat(Args[0])));
end;
procedure fpsSQRT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if x >= 0 then
Result := FloatResult(sqrt(x))
else
Result := ErrorResult(errOverflow);
end;
procedure fpsTAN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
x: TsExprFloat;
begin
x := ArgToFloat(Args[0]);
if frac(x / (pi*0.5)) = 0 then
Result := ErrorResult(errOverflow) // #NUM!
else
Result := FloatResult(tan(ArgToFloat(Args[0])));
end;
procedure fpsTANH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
begin
Result := FloatResult(tanh(ArgToFloat(Args[0])));
end;
// Builtin date/time functions
procedure fpsDATE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// DATE( year, month, day )
begin
Result := DateTimeResult(
EncodeDate(ArgToInt(Args[0]), ArgToInt(Args[1]), ArgToInt(Args[2]))
);
end;
procedure fpsDATEDIF(var Result: TsExpressionResult; const Args: TsExprParameterArray);
{ DATEDIF( start_date, end_date, interval )
start_date <= end_date !
interval = Y - The number of complete years.
= M - The number of complete months.
= D - The number of days.
= MD - The difference between the days (months and years are ignored).
= YM - The difference between the months (days and years are ignored).
= YD - The difference between the days (years and dates are ignored). }
var
interval: String;
start_date, end_date: TDate;
begin
start_date := ArgToDateTime(Args[0]);
end_date := ArgToDateTime(Args[1]);
interval := ArgToString(Args[2]);
if end_date > start_date then
Result := ErrorResult(errOverflow)
else if interval = 'Y' then
Result := FloatResult(YearsBetween(end_date, start_date))
else if interval = 'M' then
Result := FloatResult(MonthsBetween(end_date, start_date))
else if interval = 'D' then
Result := FloatResult(DaysBetween(end_date, start_date))
else
Result := ErrorResult(errFormulaNotSupported);
end;
procedure fpsDATEVALUE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the serial number of a date. Input is a string.
// DATE( date_string )
var
d: TDateTime;
begin
if TryStrToDate(Args[0].ResString, d) then
Result := DateTimeResult(d)
else
Result := ErrorResult(errWrongType);
end;
procedure fpsDAY(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// DAY( date_value )
// date_value can be a serial number or a string
var
y,m,d: Word;
dt: TDateTime;
begin
if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then
DecodeDate(ArgToFloat(Args[0]), y,m,d)
else
if Args[0].ResultType in [rtString] then
begin
if TryStrToDate(Args[0].ResString, dt) then
DecodeDate(dt, y,m,d)
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
end;
Result := IntegerResult(d);
end;
procedure fpsHOUR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// HOUR( time_value )
// time_value can be a number or a string.
var
h, m, s, ms: Word;
t: double;
begin
if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then
DecodeTime(ArgToFloat(Args[0]), h,m,s,ms)
else
if (Args[0].ResultType in [rtString]) then
begin
if TryStrToTime(Args[0].ResString, t) then
DecodeTime(t, h,m,s,ms)
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
end;
Result := IntegerResult(h);
end;
procedure fpsMINUTE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// MINUTE( serial_number or string )
var
h, m, s, ms: Word;
t: double;
begin
if (Args[0].resultType in [rtDateTime, rtFloat, rtInteger]) then
DecodeTime(ArgToFloat(Args[0]), h,m,s,ms)
else
if (Args[0].ResultType in [rtString]) then
begin
if TryStrToTime(Args[0].ResString, t) then
DecodeTime(t, h,m,s,ms)
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
end;
Result := IntegerResult(m);
end;
procedure fpsMONTH(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// MONTH( date_value or string )
var
y,m,d: Word;
dt: TDateTime;
begin
if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then
DecodeDate(ArgToFloat(Args[0]), y,m,d)
else
if (Args[0].ResultType in [rtString]) then
begin
if TryStrToDate(Args[0].ResString, dt) then
DecodeDate(dt, y,m,d)
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
end;
Result := IntegerResult(m);
end;
procedure fpsNOW(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the current system date and time. Willrefresh the date/time value
// whenever the worksheet recalculates.
// NOW()
begin
Result := DateTimeResult(Now);
end;
procedure fpsSECOND(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// SECOND( serial_number )
var
h, m, s, ms: Word;
t: Double;
begin
if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then
DecodeTime(ArgToFloat(Args[0]), h,m,s,ms)
else
if (Args[0].ResultType in [rtString]) then
begin
if TryStrToTime(Args[0].ResString, t) then
DecodeTime(t, h,m,s,ms)
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
end;
Result := IntegerResult(s);
end;
procedure fpsTIME(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// TIME( hour, minute, second)
begin
Result := DateTimeResult(
EncodeTime(ArgToInt(Args[0]), ArgToInt(Args[1]), ArgToInt(Args[2]), 0)
);
end;
procedure fpsTIMEVALUE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the serial number of a time. Input must be a string.
// DATE( date_string )
var
t: TDateTime;
begin
if TryStrToTime(Args[0].ResString, t) then
Result := DateTimeResult(t)
else
Result := ErrorResult(errWrongType);
end;
procedure fpsTODAY(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the current system date. This function will refresh the date
// whenever the worksheet recalculates.
// TODAY()
begin
Result := DateTimeResult(Date);
end;
procedure fpsWEEKDAY(var Result: TsExpressionResult; const Args: TsExprParameterArray);
{ WEEKDAY( serial_number, [return_value] )
return_value = 1 - Returns a number from 1 (Sunday) to 7 (Saturday) (default)
= 2 - Returns a number from 1 (Monday) to 7 (Sunday).
= 3 - Returns a number from 0 (Monday) to 6 (Sunday). }
var
n: Integer;
dow: Integer;
dt: TDateTime;
begin
if Length(Args) = 2 then
n := ArgToInt(Args[1])
else
n := 1;
if Args[0].ResultType in [rtDateTime, rtFloat, rtInteger] then
dt := ArgToDateTime(Args[0])
else
if Args[0].ResultType in [rtString] then
if not TryStrToDate(Args[0].ResString, dt) then
begin
Result := ErrorResult(errWrongType);
exit;
end;
dow := DayOfWeek(dt); // Sunday = 1 ... Saturday = 7
case n of
1: ;
2: if dow > 1 then dow := dow - 1 else dow := 7;
3: if dow > 1 then dow := dow - 2 else dow := 6;
end;
Result := IntegerResult(dow);
end;
procedure fpsYEAR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// YEAR( date_value )
var
y,m,d: Word;
dt: TDateTime;
begin
if Args[0].ResultType in [rtDateTime, rtFloat, rtInteger] then
DecodeDate(ArgToFloat(Args[0]), y,m,d)
else
if Args[0].ResultType in [rtString] then
begin
if TryStrToDate(Args[0].ResString, dt) then
DecodeDate(dt, y,m,d)
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
end;
Result := IntegerResult(y);
end;
// Builtin string functions
procedure fpsCHAR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// CHAR( ascii_value )
// returns the character based on the ASCII value
var
arg: Integer;
begin
Result := ErrorResult(errWrongType);
case Args[0].ResultType of
rtInteger, rtFloat:
if Args[0].ResultType in [rtInteger, rtFloat] then
begin
arg := ArgToInt(Args[0]);
if (arg >= 0) and (arg < 256) then
Result := StringResult(AnsiToUTF8(Char(arg)));
end;
rtError:
Result := ErrorResult(Args[0].ResError);
rtEmpty:
Result.ResultType := rtEmpty;
end;
end;
procedure fpsCODE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// CODE( text )
// returns the ASCII value of a character or the first character in a string.
var
s: String;
ch: Char;
begin
s := ArgToString(Args[0]);
if s = '' then
Result := ErrorResult(errWrongType)
else
begin
ch := UTF8ToAnsi(s)[1];
Result := IntegerResult(ord(ch));
end;
end;
procedure fpsCONCATENATE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// CONCATENATE( text1, text2, ... text_n )
// Joins two or more strings together
var
s: String;
i: Integer;
begin
s := '';
for i:=0 to Length(Args)-1 do
begin
if Args[i].ResultType = rtError then
begin
Result := ErrorResult(Args[i].ResError);
exit;
end;
s := s + ArgToString(Args[i]);
end;
Result := StringResult(s);
end;
procedure fpsLEFT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// LEFT( text, [number_of_characters] )
// extracts a substring from a string, starting from the left-most character
var
s: String;
count: Integer;
begin
s := Args[0].ResString;
if s = '' then
Result.ResultType := rtEmpty
else
begin
if Length(Args) = 1 then
count := 1
else
if Args[1].ResultType in [rtInteger, rtFloat] then
count := ArgToInt(Args[1])
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
Result := StringResult(UTF8LeftStr(s, count));
end;
end;
procedure fpsLEN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// LEN( text )
// returns the length of the specified string.
begin
Result := IntegerResult(UTF8Length(Args[0].ResString));
end;
procedure fpsLOWER(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// LOWER( text )
// converts all letters in the specified string to lowercase. If there are
// characters in the string that are not letters, they are not affected.
begin
Result := StringResult(UTF8Lowercase(Args[0].ResString));
end;
procedure fpsMID(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// MID( text, start_position, number_of_characters )
// extracts a substring from a string (starting at any position).
begin
Result := StringResult(UTF8Copy(Args[0].ResString, ArgToInt(Args[1]), ArgToInt(Args[2])));
end;
procedure fpsREPLACE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// REPLACE( old_text, start, number_of_chars, new_text )
// replaces a sequence of characters in a string with another set of characters
var
sOld, sNew, s1, s2: String;
start: Integer;
count: Integer;
begin
sOld := Args[0].ResString;
start := ArgToInt(Args[1]);
count := ArgToInt(Args[2]);
sNew := Args[3].ResString;
s1 := UTF8Copy(sOld, 1, start-1);
s2 := UTF8Copy(sOld, start+count, UTF8Length(sOld));
Result := StringResult(s1 + sNew + s2);
end;
procedure fpsRIGHT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// RIGHT( text, [number_of_characters] )
// extracts a substring from a string, starting from the last character
var
s: String;
count: Integer;
begin
s := Args[0].ResString;
if s = '' then
Result.ResultType := rtEmpty
else begin
if Length(Args) = 1 then
count := 1
else
if Args[1].ResultType in [rtInteger, rtFloat] then
count := ArgToInt(Args[1])
else
begin
Result := ErrorResult(errWrongType);
exit;
end;
Result := StringResult(UTF8RightStr(s, count));
end;
end;
procedure fpsSUBSTITUTE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// SUBSTITUTE( text, old_text, new_text, [nth_appearance] )
// replaces a set of characters with another.
var
sOld: String;
sNew: String;
s1, s2: String;
n: Integer;
s: String;
p: Integer;
begin
s := Args[0].ResString;
sOld := ArgToString(Args[1]);
sNew := ArgToString(Args[2]);
if Length(Args) = 4 then
begin
n := ArgToInt(Args[3]); // THIS PART NOT YET CHECKED !!!!!!
if n <= 0 then
begin
Result := ErrorResult(errWrongType);
exit;
end;
p := UTF8Pos(sOld, s);
while (n > 1) do begin
p := UTF8Pos(sOld, s, p+1);
dec(n);
end;
if p > 0 then begin
s1 := UTF8Copy(s, 1, p-1);
s2 := UTF8Copy(s, p+UTF8Length(sOld), UTF8Length(s));
s := s1 + sNew + s2;
end;
Result := StringResult(s);
end else
Result := StringResult(UTF8StringReplace(s, sOld, sNew, [rfReplaceAll]));
end;
procedure fpsTRIM(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// TRIM( text )
// returns a text value with the leading and trailing spaces removed
begin
Result := StringResult(UTF8Trim(Args[0].ResString));
end;
procedure fpsUPPER(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// UPPER( text )
// converts all letters in the specified string to uppercase. If there are
// characters in the string that are not letters, they are not affected.
begin
Result := StringResult(UTF8Uppercase(Args[0].ResString));
end;
procedure fpsVALUE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// VALUE( text )
// converts a text value that represents a number to a number.
var
x: Double;
n: Integer;
s: String;
begin
s := ArgToString(Args[0]);
if TryStrToInt(s, n) then
Result := IntegerResult(n)
else
if TryStrToFloat(s, x, ExprFormatSettings) then
Result := FloatResult(x)
else
Result := ErrorResult(errWrongType);
end;
{ Builtin logical functions }
procedure fpsAND(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// AND( condition1, [condition2], ... )
// up to 30 parameters. At least 1 parameter.
var
i: Integer;
b: Boolean;
begin
b := true;
for i:=0 to High(Args) do
b := b and Args[i].ResBoolean;
Result.ResBoolean := b;
end;
procedure fpsFALSE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// FALSE ()
begin
Unused(Args);
Result.ResBoolean := false;
end;
procedure fpsIF(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// IF( condition, value_if_true, [value_if_false] )
begin
if Length(Args) > 2 then
begin
if Args[0].ResBoolean then
Result := Args[1]
else
Result := Args[2];
end else
begin
if Args[0].ResBoolean then
Result := Args[1]
else
Result.ResBoolean := false;
end;
end;
procedure fpsNOT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// NOT( condition )
begin
Result.ResBoolean := not Args[0].ResBoolean;
end;
procedure fpsOR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// OR( condition1, [condition2], ... )
// up to 30 parameters. At least 1 parameter.
var
i: Integer;
b: Boolean;
begin
b := false;
for i:=0 to High(Args) do
b := b or Args[i].ResBoolean;
Result.ResBoolean := b;
end;
procedure fpsTRUE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// TRUE()
begin
Unused(Args);
Result.ResBoolean := true;
end;
{ Builtin statistical functions }
procedure ArgsToFloatArray(const Args: TsExprParameterArray; out AData: TsExprFloatArray);
const
BLOCKSIZE = 128;
var
i, n: Integer;
r, c: Cardinal;
cell: PCell;
arg: TsExpressionResult;
begin
SetLength(AData, BLOCKSIZE);
n := 0;
for i:=0 to High(Args) do
begin
arg := Args[i];
if arg.ResultType = rtCellRange then
for r := arg.ResCellRange.Row1 to arg.ResCellRange.Row2 do
for c := arg.ResCellRange.Col1 to arg.ResCellRange.Col2 do
begin
cell := arg.Worksheet.FindCell(r, c);
if (cell <> nil) and (cell^.ContentType in [cctNumber, cctDateTime]) then
begin
case cell^.ContentType of
cctNumber : AData[n] := cell^.NumberValue;
cctDateTime : AData[n] := cell^.DateTimeValue
end;
inc(n);
if n = Length(AData) then SetLength(AData, length(AData) + BLOCKSIZE);
end;
end
else
if (arg.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell]) then
begin
AData[n] := ArgToFloat(arg);
inc(n);
if n = Length(AData) then SetLength(AData, Length(AData) + BLOCKSIZE);
end;
end;
SetLength(AData, n);
end;
procedure fpsAVEDEV(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Average value of absolute deviations of data from their mean.
// AVEDEV( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
m: TsExprFloat;
i: Integer;
begin
ArgsToFloatArray(Args, data);
m := Mean(data);
for i:=0 to High(data) do // replace data by their average deviation from the mean
data[i] := abs(data[i] - m);
Result.ResFloat := Mean(data);
end;
procedure fpsAVERAGE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// AVERAGE( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
Result.ResFloat := Mean(data);
end;
procedure fpsCOUNT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
{ counts the number of cells that contain numbers as well as the number of
arguments that contain numbers.
COUNT( value1, [value2, ... value_n] ) }
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
Result.ResInteger := Length(data);
end;
procedure fpsCOUNTA(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Counts the number of cells that are not empty as well as the number of
// arguments that contain values
// COUNTA( value1, [value2, ... value_n] )
var
i, n: Integer;
r, c: Cardinal;
cell: PCell;
arg: TsExpressionResult;
begin
n := 0;
for i:=0 to High(Args) do
begin
arg := Args[i];
case arg.ResultType of
rtInteger, rtFloat, rtDateTime, rtBoolean:
inc(n);
rtString:
if arg.ResString <> '' then inc(n);
rtError:
if arg.ResError <> errOK then inc(n);
rtCell:
begin
cell := ArgToCell(arg);
if cell <> nil then
case cell^.ContentType of
cctNumber, cctDateTime, cctBool: inc(n);
cctUTF8String: if cell^.UTF8StringValue <> '' then inc(n);
cctError: if cell^.ErrorValue <> errOK then inc(n);
end;
end;
rtCellRange:
for r := arg.ResCellRange.Row1 to arg.ResCellRange.Row2 do
for c := arg.ResCellRange.Col1 to arg.ResCellRange.Col2 do
begin
cell := arg.Worksheet.FindCell(r, c);
if (cell <> nil) then
case cell^.ContentType of
cctNumber, cctDateTime, cctBool : inc(n);
cctUTF8String: if cell^.UTF8StringValue <> '' then inc(n);
cctError: if cell^.ErrorValue <> errOK then inc(n);
end;
end;
end;
end;
Result.ResInteger := n;
end;
procedure fpsCOUNTBLANK(var Result: TsExpressionResult; const Args: TsExprParameterArray);
{ Counts the number of empty cells in a range.
COUNTBLANK( range )
"range" is the range of cells to count empty cells. }
var
n: Integer;
r, c: Cardinal;
cell: PCell;
arg: TsExpressionResult;
begin
n := 0;
case Args[0].ResultType of
rtEmpty:
inc(n);
rtCell:
begin
cell := ArgToCell(Args[0]);
if cell = nil then
inc(n)
else
case cell^.ContentType of
cctNumber, cctDateTime, cctBool: ;
cctUTF8String: if cell^.UTF8StringValue = '' then inc(n);
cctError: if cell^.ErrorValue = errOK then inc(n);
end;
end;
rtCellRange:
for r := Args[0].ResCellRange.Row1 to Args[0].ResCellRange.Row2 do
for c := Args[0].ResCellRange.Col1 to Args[0].ResCellRange.Col2 do begin
cell := Args[0].Worksheet.FindCell(r, c);
if cell = nil then
inc(n)
else
case cell^.ContentType of
cctNumber, cctDateTime, cctBool: ;
cctUTF8String: if cell^.UTF8StringValue = '' then inc(n);
cctError: if cell^.ErrorValue = errOK then inc(n);
end;
end;
end;
Result.ResInteger := n;
end;
procedure fpsMAX(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// MAX( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
Result.ResFloat := MaxValue(data);
end;
procedure fpsMIN(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// MIN( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
Result.ResFloat := MinValue(data);
end;
procedure fpsPRODUCT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// PRODUCT( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
i: Integer;
p: TsExprFloat;
begin
ArgsToFloatArray(Args, data);
p := 1.0;
for i := 0 to High(data) do
p := p * data[i];
Result.ResFloat := p;
end;
procedure fpsSTDEV(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the standard deviation of a population based on a sample of numbers
// of numbers.
// STDEV( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
if Length(data) > 1 then
Result.ResFloat := StdDev(data)
else
begin
Result.ResultType := rtError;
Result.ResError := errDivideByZero;
end;
end;
procedure fpsSTDEVP(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the standard deviation of a population based on an entire population
// STDEVP( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
if Length(data) > 0 then
Result.ResFloat := PopnStdDev(data)
else
begin
Result.ResultType := rtError;
Result.ResError := errDivideByZero;
end;
end;
procedure fpsSUM(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// SUM( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
Result.ResFloat := Sum(data);
end;
procedure fpsSUMSQ(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the sum of the squares of a series of values.
// SUMSQ( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
Result.ResFloat := SumOfSquares(data);
end;
procedure fpsVAR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the variance of a population based on a sample of numbers.
// VAR( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
if Length(data) > 1 then
Result.ResFloat := Variance(data)
else
begin
Result.ResultType := rtError;
Result.ResError := errDivideByZero;
end;
end;
procedure fpsVARP(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Returns the variance of a population based on an entire population of numbers.
// VARP( value1, [value2, ... value_n] )
var
data: TsExprFloatArray;
begin
ArgsToFloatArray(Args, data);
if Length(data) > 0 then
Result.ResFloat := PopnVariance(data)
else
begin
Result.ResultType := rtError;
Result.ResError := errDivideByZero;
end;
end;
{ Builtin info functions }
{ !!!!!!!!!!!!!! not working !!!!!!!!!!!!!!!!!!!!!! }
{ !!!!!!!!!!!!!! needs localized strings !!!!!!!!!!! }
procedure fpsCELL(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// CELL( type, [range] )
{ from http://www.techonthenet.com/excel/formulas/cell.php:
"type" is the type of information that we retrieve for the cell and can have
one of the following values:
Value Explanation
------------- --------------------------------------------------------------
"address" Address of the cell. If the cell refers to a range, it is the
first cell in the range.
"col" Column number of the cell.
"color" Returns 1 if the color is a negative value; Otherwise it returns 0.
"contents" Contents of the upper-left cell.
"filename" Filename of the file that contains reference.
"format" Number format of the cell according to:
"G" General
"F0" 0
",0" #,##0
"F2" 0.00
",2" #,##0.00
"C0" $#,##0_);($#,##0)
"C0-" $#,##0_);[Red]($#,##0)
"C2" $#,##0.00_);($#,##0.00)
"C2-" $#,##0.00_);[Red]($#,##0.00)
"P0" 0%
"P2" 0.00%
"S2" 0.00E+00
"G" # ?/? or # ??/??
"D4" m/d/yy or m/d/yy h:mm or mm/dd/yy
"D1" d-mmm-yy or dd-mmm-yy
"D2" d-mmm or dd-mmm
"D3" mmm-yy
"D5" mm/dd
"D6" h:mm:ss AM/PM
"D7" h:mm AM/PM
"D8" h:mm:ss
"D9" h:mm
"parentheses" Returns 1 if the cell is formatted with parentheses;
Otherwise, it returns 0.
"prefix" Label prefix for the cell.
- Returns a single quote (') if the cell is left-aligned.
- Returns a double quote (") if the cell is right-aligned.
- Returns a caret (^) if the cell is center-aligned.
- Returns a back slash (\) if the cell is fill-aligned.
- Returns an empty text value for all others.
"protect" Returns 1 if the cell is locked. Returns 0 if the cell is not locked.
"row" Row number of the cell.
"type" Returns "b" if the cell is empty.
Returns "l" if the cell contains a text constant.
Returns "v" for all others.
"width" Column width of the cell, rounded to the nearest integer.
!!!! NOT ALL OF THEM ARE SUPPORTED HERE !!!
"range" is optional in Excel. It is the cell (or range) that you wish to retrieve
information for. If the range parameter is omitted, the CELL function will
assume that you are retrieving information for the last cell that was changed.
"range" is NOT OPTIONAL here because we don't know the last cell changed !!!
}
var
stype: String;
r1,r2, c1,c2: Cardinal;
cell: PCell;
res: TsExpressionResult;
begin
if Length(Args)=1 then
begin
// This case is not supported by us, but it is by Excel.
// Therefore the error is not quite correct...
Result := ErrorResult(errIllegalRef);
exit;
end;
stype := lowercase(ArgToString(Args[0]));
case Args[1].ResultType of
rtCell:
begin
cell := ArgToCell(Args[1]);
r1 := Args[1].ResRow;
c1 := Args[1].ResCol;
r2 := r1;
c2 := c1;
end;
rtCellRange:
begin
r1 := Args[1].ResCellRange.Row1;
r2 := Args[1].ResCellRange.Row2;
c1 := Args[1].ResCellRange.Col1;
c2 := Args[1].ResCellRange.Col2;
cell := Args[1].Worksheet.FindCell(r1, c1);
end;
else
Result := ErrorResult(errWrongType);
exit;
end;
if stype = 'address' then
Result := StringResult(GetCellString(r1, c1, []))
else
if stype = 'col' then
Result := IntegerResult(c1+1)
else
if stype = 'color' then
begin
if (cell <> nil) and (cell^.NumberFormat = nfCurrencyRed) then
Result := IntegerResult(1)
else
Result := IntegerResult(0);
end else
if stype = 'contents' then
begin
if cell = nil then
Result := IntegerResult(0)
else
case cell^.ContentType of
cctNumber : if frac(cell^.NumberValue) = 0 then
Result := IntegerResult(trunc(cell^.NumberValue))
else
Result := FloatResult(cell^.NumberValue);
cctDateTime : Result := DateTimeResult(cell^.DateTimeValue);
cctUTF8String : Result := StringResult(cell^.UTF8StringValue);
cctBool : Result := BooleanResult(cell^.BoolValue);
cctError : Result := ErrorResult(cell^.ErrorValue);
end;
end else
if stype = 'filename' then
Result := Stringresult(
ExtractFilePath(Args[1].Worksheet.Workbook.FileName) + '[' +
ExtractFileName(Args[1].Worksheet.Workbook.FileName) + ']' +
Args[1].Worksheet.Name
)
else
if stype = 'format' then begin
Result := StringResult('G');
if cell <> nil then
case cell^.NumberFormat of
nfGeneral:
Result := StringResult('G');
nfFixed:
if cell^.NumberFormatStr= '0' then Result := StringResult('0') else
if cell^.NumberFormatStr = '0.00' then Result := StringResult('F0');
nfFixedTh:
if cell^.NumberFormatStr = '#,##0' then Result := StringResult(',0') else
if cell^.NumberFormatStr = '#,##0.00' then Result := StringResult(',2');
nfPercentage:
if cell^.NumberFormatStr = '0%' then Result := StringResult('P0') else
if cell^.NumberFormatStr = '0.00%' then Result := StringResult('P2');
nfExp:
if cell^.NumberFormatStr = '0.00E+00' then Result := StringResult('S2');
nfShortDate, nfLongDate, nfShortDateTime:
Result := StringResult('D4');
nfLongTimeAM:
Result := StringResult('D6');
nfShortTimeAM:
Result := StringResult('D7');
nfLongTime:
Result := StringResult('D8');
nfShortTime:
Result := StringResult('D9');
end;
end else
if stype = 'prefix' then
begin
Result := StringResult('');
if (cell^.ContentType = cctUTF8String) then
case cell^.HorAlignment of
haLeft : Result := StringResult('''');
haCenter: Result := StringResult('^');
haRight : Result := StringResult('"');
end;
end else
if stype = 'row' then
Result := IntegerResult(r1+1)
else
if stype = 'type' then begin
if (cell = nil) or (cell^.ContentType = cctEmpty) then
Result := StringResult('b')
else if cell^.ContentType = cctUTF8String then begin
if (cell^.UTF8StringValue = '')
then Result := StringResult('b')
else Result := StringResult('l');
end else
Result := StringResult('v');
end else
if stype = 'width' then
Result := FloatResult(Args[1].Worksheet.GetColWidth(c1))
else
Result := ErrorResult(errWrongType);
end;
procedure fpsISBLANK(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISBLANK( value )
// Checks for blank or null values.
// "value" is the value that you want to test.
// If "value" is blank, this function will return TRUE.
// If "value" is not blank, the function will return FALSE.
var
cell: PCell;
begin
case Args[0].ResultType of
rtEmpty : Result := BooleanResult(true);
rtString: Result := BooleanResult(Result.ResString = '');
rtCell : begin
cell := ArgToCell(Args[0]);
if (cell = nil) or (cell^.ContentType = cctEmpty) then
Result := BooleanResult(true)
else
Result := BooleanResult(false);
end;
end;
end;
procedure fpsISERR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISERR( value )
// If "value" is an error value (except #N/A), this function will return TRUE.
// Otherwise, it will return FALSE.
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell <> nil) and (cell^.ContentType = cctError) and (cell^.ErrorValue <> errArgError)
then Result := BooleanResult(true);
end else
if (Args[0].ResultType = rtError) and (Args[0].ResError <> errArgError) then
Result := BooleanResult(true);
end;
procedure fpsISERROR(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISERROR( value )
// If "value" is an error value (#N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?
// or #NULL), this function will return TRUE. Otherwise, it will return FALSE.
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell <> nil) and (cell^.ContentType = cctError) and (cell^.ErrorValue <= errArgError)
then Result := BooleanResult(true);
end else
if (Args[0].ResultType = rtError) then
Result := BooleanResult(true);
end;
procedure fpsISLOGICAL(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISLOGICAL( value )
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell <> nil) and (cell^.ContentType = cctBool) then
Result := BooleanResult(true);
end else
if (Args[0].ResultType = rtBoolean) then
Result := BooleanResult(true);
end;
procedure fpsISNA(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISNA( value )
// If "value" is a #N/A error value , this function will return TRUE.
// Otherwise, it will return FALSE.
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell <> nil) and (cell^.ContentType = cctError) and (cell^.ErrorValue = errArgError)
then Result := BooleanResult(true);
end else
if (Args[0].ResultType = rtError) and (Args[0].ResError = errArgError) then
Result := BooleanResult(true);
end;
procedure fpsISNONTEXT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISNONTEXT( value )
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell = nil) or ((cell <> nil) and (cell^.ContentType <> cctUTF8String)) then
Result := BooleanResult(true);
end else
if (Args[0].ResultType <> rtString) then
Result := BooleanResult(true);
end;
procedure fpsISNUMBER(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISNUMBER( value )
// Tests "value" for a number (or date/time - checked with Excel).
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell <> nil) and (cell^.ContentType in [cctNumber, cctDateTime]) then
Result := BooleanResult(true);
end else
if (Args[0].ResultType in [rtFloat, rtInteger, rtDateTime]) then
Result := BooleanResult(true);
end;
procedure fpsISREF(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISREF( value )
begin
Result := BooleanResult(Args[0].ResultType in [rtCell, rtCellRange]);
end;
procedure fpsISTEXT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// ISTEXT( value )
var
cell: PCell;
begin
Result := BooleanResult(false);
if (Args[0].ResultType = rtCell) then
begin
cell := ArgToCell(Args[0]);
if (cell <> nil) and (cell^.ContentType = cctUTF8String) then
Result := BooleanResult(true);
end else
if (Args[0].ResultType = rtString) then
Result := BooleanResult(true);
end;
{------------------------------------------------------------------------------}
{@@
Registers a non-built-in function:
@param AName Name of the function as used for calling it in the spreadsheet
@param AResultType A character classifying the data type of the function result:
'I' integer
'F' floating point number
'D' date/time value
'S' string
'B' boolean value (TRUE/FALSE)
'R' cell range, can also be used for functions requiring
a cell "reference", like "CELL(..)"
@param AParamTypes A string with result type symbols for each parameter of the
function. Symbols as used for "ResultType" with these
additions:
- Use a lower-case character if a parameter is optional.
(must be at the end of the string)
- Add "+" if the last parameter type is valid for a variable
parameter count (Excel does pose a limit of 30, though).
- Use "?" if the data type should not be checked.
@param AExcelCode ID of the function needed in the xls biff file. Please see
the "OpenOffice Documentation of Microsoft Excel File Format"
section 3.11.
@param ACallBack Address of the procedure called when the formula is
calculated.
}
{------------------------------------------------------------------------------}
procedure RegisterFunction(const AName: ShortString; const AResultType: Char;
const AParamTypes: String; const AExcelCode: Integer; ACallback: TsExprFunctionCallBack);
begin
with BuiltinIdentifiers do
AddFunction(bcUser, AName, AResultType, AParamTypes, AExcelCode, ACallBack);
end;
{@@
Registers the built-in functions. Called automatically.
}
procedure RegisterStdBuiltins(AManager : TsBuiltInExpressionManager);
var
cat: TsBuiltInExprCategory;
begin
with AManager do
begin
// Math functions
cat := bcMath;
AddFunction(cat, 'ABS', 'F', 'F', INT_EXCEL_SHEET_FUNC_ABS, @fpsABS);
AddFunction(cat, 'ACOS', 'F', 'F', INT_EXCEL_SHEET_FUNC_ACOS, @fpsACOS);
AddFunction(cat, 'ACOSH', 'F', 'F', INT_EXCEL_SHEET_FUNC_ACOSH, @fpsACOSH);
AddFunction(cat, 'ASIN', 'F', 'F', INT_EXCEL_SHEET_FUNC_ASIN, @fpsASIN);
AddFunction(cat, 'ASINH', 'F', 'F', INT_EXCEL_SHEET_FUNC_ASINH, @fpsASINH);
AddFunction(cat, 'ATAN', 'F', 'F', INT_EXCEL_SHEET_FUNC_ATAN, @fpsATAN);
AddFunction(cat, 'ATANH', 'F', 'F', INT_EXCEL_SHEET_FUNC_ATANH, @fpsATANH);
AddFunction(cat, 'COS', 'F', 'F', INT_EXCEL_SHEET_FUNC_COS, @fpsCOS);
AddFunction(cat, 'COSH', 'F', 'F', INT_EXCEL_SHEET_FUNC_COSH, @fpsCOSH);
AddFunction(cat, 'DEGREES', 'F', 'F', INT_EXCEL_SHEET_FUNC_DEGREES, @fpsDEGREES);
AddFunction(cat, 'EXP', 'F', 'F', INT_EXCEL_SHEET_FUNC_EXP, @fpsEXP);
AddFunction(cat, 'INT', 'I', 'F', INT_EXCEL_SHEET_FUNC_INT, @fpsINT);
AddFunction(cat, 'LN', 'F', 'F', INT_EXCEL_SHEET_FUNC_LN, @fpsLN);
AddFunction(cat, 'LOG', 'F', 'Ff', INT_EXCEL_SHEET_FUNC_LOG, @fpsLOG);
AddFunction(cat, 'LOG10', 'F', 'F', INT_EXCEL_SHEET_FUNC_LOG10, @fpsLOG10);
AddFunction(cat, 'PI', 'F', '', INT_EXCEL_SHEET_FUNC_PI, @fpsPI);
AddFunction(cat, 'POWER', 'F', 'FF', INT_EXCEL_SHEET_FUNC_POWER, @fpsPOWER);
AddFunction(cat, 'RADIANS', 'F', 'F', INT_EXCEL_SHEET_FUNC_RADIANS, @fpsRADIANS);
AddFunction(cat, 'RAND', 'F', '', INT_EXCEL_SHEET_FUNC_RAND, @fpsRAND);
AddFunction(cat, 'ROUND', 'F', 'FF', INT_EXCEL_SHEET_FUNC_ROUND, @fpsROUND);
AddFunction(cat, 'SIGN', 'F', 'F', INT_EXCEL_SHEET_FUNC_SIGN, @fpsSIGN);
AddFunction(cat, 'SIN', 'F', 'F', INT_EXCEL_SHEET_FUNC_SIN, @fpsSIN);
AddFunction(cat, 'SINH', 'F', 'F', INT_EXCEL_SHEET_FUNC_SINH, @fpsSINH);
AddFunction(cat, 'SQRT', 'F', 'F', INT_EXCEL_SHEET_FUNC_SQRT, @fpsSQRT);
AddFunction(cat, 'TAN', 'F', 'F', INT_EXCEL_SHEET_FUNC_TAN, @fpsTAN);
AddFunction(cat, 'TANH', 'F', 'F', INT_EXCEL_SHEET_FUNC_TANH, @fpsTANH);
// Date/time
cat := bcDateTime;
AddFunction(cat, 'DATE', 'D', 'III', INT_EXCEL_SHEET_FUNC_DATE, @fpsDATE);
AddFunction(cat, 'DATEDIF', 'F', 'DDS', INT_EXCEL_SHEET_FUNC_DATEDIF, @fpsDATEDIF);
AddFunction(cat, 'DATEVALUE', 'D', 'S', INT_EXCEL_SHEET_FUNC_DATEVALUE, @fpsDATEVALUE);
AddFunction(cat, 'DAY', 'I', '?', INT_EXCEL_SHEET_FUNC_DAY, @fpsDAY);
AddFunction(cat, 'HOUR', 'I', '?', INT_EXCEL_SHEET_FUNC_HOUR, @fpsHOUR);
AddFunction(cat, 'MINUTE', 'I', '?', INT_EXCEL_SHEET_FUNC_MINUTE, @fpsMINUTE);
AddFunction(cat, 'MONTH', 'I', '?', INT_EXCEL_SHEET_FUNC_MONTH, @fpsMONTH);
AddFunction(cat, 'NOW', 'D', '', INT_EXCEL_SHEET_FUNC_NOW, @fpsNOW);
AddFunction(cat, 'SECOND', 'I', '?', INT_EXCEL_SHEET_FUNC_SECOND, @fpsSECOND);
AddFunction(cat, 'TIME' , 'D', 'III', INT_EXCEL_SHEET_FUNC_TIME, @fpsTIME);
AddFunction(cat, 'TIMEVALUE', 'D', 'S', INT_EXCEL_SHEET_FUNC_TIMEVALUE, @fpsTIMEVALUE);
AddFunction(cat, 'TODAY', 'D', '', INT_EXCEL_SHEET_FUNC_TODAY, @fpsTODAY);
AddFunction(cat, 'WEEKDAY', 'I', '?i', INT_EXCEL_SHEET_FUNC_WEEKDAY, @fpsWEEKDAY);
AddFunction(cat, 'YEAR', 'I', '?', INT_EXCEL_SHEET_FUNC_YEAR, @fpsYEAR);
// Strings
cat := bcStrings;
AddFunction(cat, 'CHAR', 'S', 'I', INT_EXCEL_SHEET_FUNC_CHAR, @fpsCHAR);
AddFunction(cat, 'CODE', 'I', 'S', INT_EXCEL_SHEET_FUNC_CODE, @fpsCODE);
AddFunction(cat, 'CONCATENATE','S','S+', INT_EXCEL_SHEET_FUNC_CONCATENATE,@fpsCONCATENATE);
AddFunction(cat, 'LEFT', 'S', 'Si', INT_EXCEL_SHEET_FUNC_LEFT, @fpsLEFT);
AddFunction(cat, 'LEN', 'I', 'S', INT_EXCEL_SHEET_FUNC_LEN, @fpsLEN);
AddFunction(cat, 'LOWER', 'S', 'S', INT_EXCEL_SHEET_FUNC_LOWER, @fpsLOWER);
AddFunction(cat, 'MID', 'S', 'SII', INT_EXCEL_SHEET_FUNC_MID, @fpsMID);
AddFunction(cat, 'REPLACE', 'S', 'SIIS', INT_EXCEL_SHEET_FUNC_REPLACE, @fpsREPLACE);
AddFunction(cat, 'RIGHT', 'S', 'Si', INT_EXCEL_SHEET_FUNC_RIGHT, @fpsRIGHT);
AddFunction(cat, 'SUBSTITUTE','S', 'SSSi', INT_EXCEL_SHEET_FUNC_SUBSTITUTE, @fpsSUBSTITUTE);
AddFunction(cat, 'TRIM', 'S', 'S', INT_EXCEL_SHEET_FUNC_TRIM, @fpsTRIM);
AddFunction(cat, 'UPPER', 'S', 'S', INT_EXCEL_SHEET_FUNC_UPPER, @fpsUPPER);
AddFunction(cat, 'VALUE', 'F', 'S', INT_EXCEL_SHEET_FUNC_VALUE, @fpsVALUE);
// Logical
cat := bcLogical;
AddFunction(cat, 'AND', 'B', 'B+', INT_EXCEL_SHEET_FUNC_AND, @fpsAND);
AddFunction(cat, 'FALSE', 'B', '', INT_EXCEL_SHEET_FUNC_FALSE, @fpsFALSE);
AddFunction(cat, 'IF', 'B', 'B?+', INT_EXCEL_SHEET_FUNC_IF, @fpsIF);
AddFunction(cat, 'NOT', 'B', 'B', INT_EXCEL_SHEET_FUNC_NOT, @fpsNOT);
AddFunction(cat, 'OR', 'B', 'B+', INT_EXCEL_SHEET_FUNC_OR, @fpsOR);
AddFunction(cat, 'TRUE', 'B', '', INT_EXCEL_SHEET_FUNC_TRUE , @fpsTRUE);
// Statistical
cat := bcStatistics;
AddFunction(cat, 'AVEDEV', 'F', '?+', INT_EXCEL_SHEET_FUNC_AVEDEV, @fpsAVEDEV);
AddFunction(cat, 'AVERAGE', 'F', '?+', INT_EXCEL_SHEET_FUNC_AVERAGE, @fpsAVERAGE);
AddFunction(cat, 'COUNT', 'I', '?+', INT_EXCEL_SHEET_FUNC_COUNT, @fpsCOUNT);
AddFunction(cat, 'COUNTA', 'I', '?+', INT_EXCEL_SHEET_FUNC_COUNTA, @fpsCOUNTA);
AddFunction(cat, 'COUNTBLANK','I', 'R', INT_EXCEL_SHEET_FUNC_COUNTBLANK, @fpsCOUNTBLANK);
AddFunction(cat, 'MAX', 'F', '?+', INT_EXCEL_SHEET_FUNC_MAX, @fpsMAX);
AddFunction(cat, 'MIN', 'F', '?+', INT_EXCEL_SHEET_FUNC_MIN, @fpsMIN);
AddFunction(cat, 'PRODUCT', 'F', '?+', INT_EXCEL_SHEET_FUNC_PRODUCT, @fpsPRODUCT);
AddFunction(cat, 'STDEV', 'F', '?+', INT_EXCEL_SHEET_FUNC_STDEV, @fpsSTDEV);
AddFunction(cat, 'STDEVP', 'F', '?+', INT_EXCEL_SHEET_FUNC_STDEVP, @fpsSTDEVP);
AddFunction(cat, 'SUM', 'F', '?+', INT_EXCEL_SHEET_FUNC_SUM, @fpsSUM);
AddFunction(cat, 'SUMSQ', 'F', '?+', INT_EXCEL_SHEET_FUNC_SUMSQ, @fpsSUMSQ);
AddFunction(cat, 'VAR', 'F', '?+', INT_EXCEL_SHEET_FUNC_VAR, @fpsVAR);
AddFunction(cat, 'VARP', 'F', '?+', INT_EXCEL_SHEET_FUNC_VARP, @fpsVARP);
// to do: CountIF, SUMIF
// Info functions
cat := bcInfo;
AddFunction(cat, 'CELL', '?', 'Sr', INT_EXCEL_SHEET_FUNC_CELL, @fpsCELL);
AddFunction(cat, 'ISBLANK', 'B', '?', INT_EXCEL_SHEET_FUNC_ISBLANK, @fpsISBLANK);
AddFunction(cat, 'ISERR', 'B', '?', INT_EXCEL_SHEET_FUNC_ISERR, @fpsISERR);
AddFunction(cat, 'ISERROR', 'B', '?', INT_EXCEL_SHEET_FUNC_ISERROR, @fpsISERROR);
AddFunction(cat, 'ISLOGICAL', 'B', '?', INT_EXCEL_SHEET_FUNC_ISLOGICAL, @fpsISLOGICAL);
AddFunction(cat, 'ISNA', 'B', '?', INT_EXCEL_SHEET_FUNC_ISNA, @fpsISNA);
AddFunction(cat, 'ISNONTEXT', 'B', '?', INT_EXCEL_SHEET_FUNC_ISNONTEXT, @fpsISNONTEXT);
AddFunction(cat, 'ISNUMBER', 'B', '?', INT_EXCEL_SHEET_FUNC_ISNUMBER, @fpsISNUMBER);
AddFunction(cat, 'ISREF', 'B', '?', INT_EXCEL_SHEET_FUNC_ISREF, @fpsISREF);
AddFunction(cat, 'ISTEXT', 'B', '?', INT_EXCEL_SHEET_FUNC_ISTEXT, @fpsISTEXT);
(*
// Lookup / reference functions
cat := bcLookup;
AddFunction(cat, 'COLUMN', 'I', 'R', INT_EXCEL_SHEET_FUNC_COLUMN, @fpsCOLUMN);
*)
end;
end;
{ TsBuiltInExprIdentifierDef }
procedure TsBuiltInExprIdentifierDef.Assign(Source: TPersistent);
begin
inherited Assign(Source);
if Source is TsBuiltInExprIdentifierDef then
FCategory := (Source as TsBuiltInExprIdentifierDef).Category;
end;
initialization
ExprFormatSettings := DefaultFormatSettings;
ExprFormatSettings.DecimalSeparator := '.';
ExprFormatSettings.ListSeparator := ',';
RegisterStdBuiltins(BuiltinIdentifiers);
finalization
FreeBuiltins;
end.