
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7555 8e941d3f-bd1b-0410-a28a-d453659cc2b4
5008 lines
145 KiB
ObjectPascal
5008 lines
145 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
|
|
- generalize scanner and parser to allow localized decimal and list separators
|
|
- add to spreadsheet format to parser to take account of formula "dialect"
|
|
(see OpenDocument using [] around cell addresses)
|
|
|
|
******************************************************************************}
|
|
|
|
// To do:
|
|
// Remove exceptions, use error message strings instead
|
|
// Cell reference not working (--> formula CELL!)
|
|
// Keep spaces in formula
|
|
|
|
{$mode objfpc}
|
|
{$H+}
|
|
unit fpsExprParser;
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, contnrs, fpstypes, fpsrpn;
|
|
|
|
type
|
|
{ Tokens }
|
|
|
|
TsTokenType = (
|
|
// ttCell, ttCellRange, ttSheetName, ttCellRangeODS,
|
|
ttNumber, ttString, ttIdentifier, ttSpreadsheetAddress,
|
|
ttPlus, ttMinus, ttMul, ttDiv, ttConcat, ttPercent, ttPower, ttLeft, ttRight,
|
|
ttLessThan, ttLargerThan, ttEqual, ttNotEqual, ttLessThanEqual, ttLargerThanEqual,
|
|
ttListSep, ttQuote, ttTrue, ttFalse, ttMissingArg, ttError, 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
|
|
EGeneralExprParserError = Exception;
|
|
|
|
// Forward declarations
|
|
TsExpressionParser = class;
|
|
TsBuiltInExpressionManager = class;
|
|
TsExprNode = class;
|
|
|
|
TsResultType = (rtEmpty, rtBoolean, rtInteger, rtFloat, rtDateTime, rtString,
|
|
rtCell, rtCellRange, rtHyperlink, rtError, rtMissingArg, rtAny);
|
|
TsResultTypes = set of TsResultType;
|
|
|
|
TsExpressionResult = record
|
|
Worksheet : TsBasicWorksheet; // Worksheet containing the calculated cell
|
|
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;
|
|
ResSheetIndex : Integer);
|
|
rtCellRange : (ResCellRange : TsCellRange3D);
|
|
rtHyperlink : ();
|
|
rtString : ();
|
|
end;
|
|
PsExpressionResult = ^TsExpressionResult;
|
|
TsExprParameterArray = array of TsExpressionResult;
|
|
|
|
{ Proceudre executed when iterating through all nodes (Parser.IterateNodes).
|
|
The procedure sets the parameter MustRebuildFormula to true if the
|
|
text formula has to be rebuilt. }
|
|
TsExprNodeProc = procedure(ANode: TsExprNode; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: Boolean);
|
|
|
|
{ TsExprNode }
|
|
TsExprNode = class(TObject)
|
|
private
|
|
FParser: TsExpressionParser;
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); virtual; abstract;
|
|
function HasError(out AResult: TsExpressionResult): boolean; virtual;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; virtual; abstract;
|
|
function AsString: string; virtual; abstract;
|
|
procedure Check; virtual; //abstract;
|
|
function Has3DLink: Boolean; virtual;
|
|
procedure IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: Boolean); virtual;
|
|
function NodeType: TsResultType; virtual; abstract;
|
|
function NodeValue: TsExpressionResult;
|
|
property Parser: TsExpressionParser read FParser;
|
|
end;
|
|
|
|
TsExprArgumentArray = array of TsExprNode;
|
|
|
|
{ TsBinaryOperationExprNode }
|
|
TsBinaryOperationExprNode = class(TsExprNode)
|
|
private
|
|
FLeft: TsExprNode;
|
|
FRight: TsExprNode;
|
|
protected
|
|
function HasError(out AResult: TsExpressionResult): Boolean; override;
|
|
public
|
|
constructor Create(AParser: TsExpressionParser; ALeft, ARight: TsExprNode);
|
|
destructor Destroy; override;
|
|
function Has3DLink: Boolean; override;
|
|
procedure IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: boolean); override;
|
|
property Left: TsExprNode read FLeft;
|
|
property Right: TsExprNode read FRight;
|
|
end;
|
|
TsBinaryOperationExprNodeClass = class of TsBinaryOperationExprNode;
|
|
|
|
{ TsBooleanOperationExprNode }
|
|
TsBooleanOperationExprNode = class(TsBinaryOperationExprNode)
|
|
public
|
|
function NodeType: TsResultType; override;
|
|
end;
|
|
|
|
{ TsBooleanResultExprNode }
|
|
TsBooleanResultExprNode = class(TsBinaryOperationExprNode)
|
|
public
|
|
function NodeType: TsResultType; override;
|
|
end;
|
|
TsBooleanResultExprNodeClass = class of TsBooleanResultExprNode;
|
|
|
|
{ TsEqualExprNode }
|
|
TsEqualExprNode = class(TsBooleanResultExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
end;
|
|
|
|
{ TsNotEqualExprNode }
|
|
TsNotEqualExprNode = class(TsEqualExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
end;
|
|
|
|
{ TsOrderingExprNode }
|
|
TsOrderingExprNode = class(TsBooleanResultExprNode);
|
|
|
|
{ TsLessExprNode }
|
|
TsLessExprNode = class(TsOrderingExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
end;
|
|
|
|
{ TsGreaterExprNode }
|
|
TsGreaterExprNode = class(TsOrderingExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
end;
|
|
|
|
{ TsLessEqualExprNode }
|
|
TsLessEqualExprNode = class(TsGreaterExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
end;
|
|
|
|
{ TsGreaterEqualExprNode }
|
|
TsGreaterEqualExprNode = class(TsLessExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
end;
|
|
|
|
{ TsConcatExprNode }
|
|
TsConcatExprNode = class(TsBinaryOperationExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string ; override;
|
|
function NodeType: TsResultType; override;
|
|
end;
|
|
|
|
{ TsMathOperationExprNode }
|
|
TsMathOperationExprNode = class(TsBinaryOperationExprNode)
|
|
public
|
|
function NodeType: TsResultType; override;
|
|
end;
|
|
|
|
{ TsAddExprNode }
|
|
TsAddExprNode = class(TsMathOperationExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string ; override;
|
|
end;
|
|
|
|
{ TsSubtractExprNode }
|
|
TsSubtractExprNode = class(TsMathOperationExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string ; override;
|
|
end;
|
|
|
|
{ TsMultiplyExprNode }
|
|
TsMultiplyExprNode = class(TsMathOperationExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string ; override;
|
|
end;
|
|
|
|
{ TsDivideExprNode }
|
|
TsDivideExprNode = class(TsMathOperationExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: 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(out AResult: 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;
|
|
public
|
|
constructor Create(AParser: TsExpressionParser; AOperand: TsExprNode);
|
|
procedure Check; override;
|
|
destructor Destroy; override;
|
|
property Operand: TsExprNode read FOperand;
|
|
end;
|
|
|
|
{ TsUPlusExprNode }
|
|
TsUPlusExprNode = class(TsUnaryOperationExprNode)
|
|
protected
|
|
procedure GetNodeValue(out 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(out 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(out 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(out 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 GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
constructor CreateString(AParser: TsExpressionParser; AValue: String);
|
|
constructor CreateInteger(AParser: TsExpressionParser; AValue: Int64);
|
|
constructor CreateDateTime(AParser: TsExpressionParser; AValue: TDateTime);
|
|
constructor CreateFloat(AParser: TsExpressionParser; AValue: TsExprFloat);
|
|
constructor CreateBoolean(AParser: TsExpressionParser; AValue: Boolean);
|
|
constructor CreateError(AParser: TsExpressionParser; AValue: TsErrorValue); overload;
|
|
constructor CreateError(AParser: TsExpressionParser; AValue: String); overload;
|
|
function AsString: string; override;
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function NodeType : TsResultType; override;
|
|
// For inspection
|
|
property ConstValue: TsExpressionResult read FValue;
|
|
end;
|
|
|
|
{ TsMissingArgExprNode }
|
|
TsMissingArgExprNode = class(TsExprNode)
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
function AsString: String; override;
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function NodeType: TsResultType; override;
|
|
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;
|
|
function GetFormatSettings: TFormatSettings;
|
|
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(out AResult: TsExpressionResult); override;
|
|
public
|
|
constructor CreateIdentifier(AParser: TsExpressionParser; AID: TsExprIdentifierDef);
|
|
function NodeType: TsResultType; override;
|
|
property Identifier: TsExprIdentifierDef read FID;
|
|
end;
|
|
|
|
{ TsVariableExprNode }
|
|
TsVariableExprNode = class(TsIdentifierExprNode)
|
|
public
|
|
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(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef; const Args: TsExprArgumentArray); virtual;
|
|
destructor Destroy; override;
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: String; override;
|
|
procedure Check; override;
|
|
function Has3DLink: Boolean; override;
|
|
procedure IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: Boolean); override;
|
|
property ArgumentNodes: TsExprArgumentArray read FArgumentNodes;
|
|
property ArgumentParams: TsExprParameterArray read FArgumentParams;
|
|
end;
|
|
|
|
{ TsFunctionCallBackExprNode }
|
|
TsFunctionCallBackExprNode = class(TsFunctionExprNode)
|
|
private
|
|
FCallBack: TsExprFunctionCallBack;
|
|
protected
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
constructor CreateFunction(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef; const Args: TsExprArgumentArray); override;
|
|
property CallBack: TsExprFunctionCallBack read FCallBack;
|
|
end;
|
|
|
|
{ TFPFunctionEventHandlerExprNode }
|
|
TFPFunctionEventHandlerExprNode = class(TsFunctionExprNode)
|
|
private
|
|
FCallBack: TsExprFunctionEvent;
|
|
protected
|
|
procedure GetNodeValue(out Result: TsExpressionResult); override;
|
|
public
|
|
constructor CreateFunction(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef; const Args: TsExprArgumentArray); override;
|
|
property CallBack: TsExprFunctionEvent read FCallBack;
|
|
end;
|
|
|
|
{ TsCellExprNode }
|
|
TsCellExprNode = class(TsExprNode)
|
|
private
|
|
FWorksheet: TsBasicWorksheet; // sheet containing the formula
|
|
FRow, FCol: Cardinal; // row/col of referenced cell
|
|
FFlags: TsRelFlags; // abs/rel flags of reference
|
|
// FCell: PCell; // cell which contains the formula
|
|
FSheetIndex: Integer; // index of referenced other sheet
|
|
FHas3DLink: Boolean;
|
|
FIsRef: Boolean;
|
|
FError: TsErrorValue;
|
|
protected
|
|
function GetCol: Cardinal;
|
|
function GetRow: Cardinal;
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
function GetQuotedSheetName: String;
|
|
public
|
|
constructor Create(AParser: TsExpressionParser; AWorksheet: TsBasicWorksheet;
|
|
ASheetName: String; ARow, ACol: Cardinal; AFlags: TsRelFlags);
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: string; override;
|
|
procedure Check; override;
|
|
function GetSheet: TsBasicWorksheet;
|
|
function GetSheetIndex: Integer;
|
|
function GetSheetName: String;
|
|
function GetWorkbook: TsBasicWorkbook;
|
|
function Has3DLink: Boolean; override;
|
|
procedure IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: Boolean); override;
|
|
function NodeType: TsResultType; override;
|
|
procedure SetSheetIndex(AIndex: Integer);
|
|
property Col: Cardinal read FCol write FCol; // Be careful when modifying Col and Row
|
|
property Row: Cardinal read FRow write FRow;
|
|
property Error: TsErrorValue read FError write FError;
|
|
property Worksheet: TsBasicWorksheet read FWorksheet;
|
|
end;
|
|
|
|
{ TsCellRangeExprNode }
|
|
TsCellRangeIndex = 1..2;
|
|
|
|
TsCellRangeExprNode = class(TsExprNode)
|
|
private
|
|
FWorksheet: TsBasicWorksheet;
|
|
FRow: array[TsCellRangeIndex] of Cardinal;
|
|
FCol: array[TsCellRangeIndex] of Cardinal;
|
|
FSheetIndex: array[TsCellRangeIndex] of Integer;
|
|
FFlags: TsRelFlags;
|
|
F3dRange: Boolean;
|
|
FError: TsErrorValue;
|
|
function GetRange: TsCellRange;
|
|
procedure SetRange(const ARange: TsCellRange);
|
|
protected
|
|
function GetCol(AIndex: TsCellRangeIndex): Cardinal;
|
|
function GetRow(AIndex: TsCellRangeIndex): Cardinal;
|
|
procedure GetNodeValue(out AResult: TsExpressionResult); override;
|
|
public
|
|
constructor Create(AParser: TsExpressionParser; AWorksheet: TsBasicWorksheet;
|
|
ASheet1, ASheet2: String; ARange: TsCellRange; AFlags: TsRelFlags);
|
|
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
|
|
function AsString: String; override;
|
|
procedure Check; override;
|
|
function GetSheet(AIndex: TsCellRangeIndex): TsBasicWorksheet;
|
|
function GetSheetIndex(AIndex: TsCellRangeIndex): Integer;
|
|
function GetWorkbook: TsBasicWorkbook;
|
|
function Has3DLink: Boolean; override;
|
|
procedure IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: Boolean); override;
|
|
function NodeType: TsResultType; override;
|
|
procedure SetSheetIndex(AIndex: TsCellRangeIndex; AValue: Integer);
|
|
property Error: TsErrorValue read FError write FError;
|
|
property Range: TsCellRange read GetRange write SetRange; // Be careful!
|
|
property Workbook: TsBasicWorkbook read GetWorkbook;
|
|
property Worksheet: TsBasicWorksheet read FWorksheet;
|
|
end;
|
|
|
|
{ TsExpressionScanner }
|
|
TsExpressionScanner = class(TObject)
|
|
FSource : String;
|
|
LSource,
|
|
FPos: Integer;
|
|
FChar: PChar;
|
|
FToken: String;
|
|
FTokenType: TsTokenType;
|
|
FSheetNameTerminator: Char;
|
|
FSavedSheetNameTerminator: Char;
|
|
FCellRange: TsCellRange;
|
|
FFlags: TsRelFlags;
|
|
FSheet1, FSheet2: String;
|
|
private
|
|
FParser: TsExpressionParser;
|
|
function GetCurrentChar: Char;
|
|
procedure ScanError(Msg: String);
|
|
protected
|
|
procedure SetSource(const AValue: String); virtual;
|
|
function DoCellRangeODS: TsTokenType;
|
|
function DoError: TsTokenType;
|
|
function DoIdentifier: TsTokenType;
|
|
function DoNumber: TsTokenType;
|
|
function DoDelimiter: TsTokenType;
|
|
// function DoSquareBracket: 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(AParser: TsExpressionParser);
|
|
procedure GetCellRangeParamsODS(out ASheet1, ASheet2: String;
|
|
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags);
|
|
function GetToken: TsTokenType;
|
|
property Token: String read FToken;
|
|
property TokenCellRange: TsCellRange read FCellRange;
|
|
property TokenFlags: TsRelFlags read FFlags;
|
|
property TokenSheet1: String read FSheet1;
|
|
property TokenSheet2: String read FSheet2;
|
|
property TokenType: TsTokenType read FTokenType;
|
|
property Source: String read FSource write SetSource;
|
|
property Pos: Integer read FPos;
|
|
property CurrentChar: Char read GetCurrentChar;
|
|
property SheetnameTerminator: char read FSheetNameTerminator write FSheetNameTerminator;
|
|
end;
|
|
|
|
EExprScanner = class(EGeneralExprParserError);
|
|
PFormatSettings = ^TFormatSettings;
|
|
|
|
{ TsExpressionParser }
|
|
TsExpressionParser = class
|
|
private
|
|
FBuiltIns: TsBuiltInExprCategories;
|
|
FExpression: String;
|
|
FScanner: TsExpressionScanner;
|
|
FExprNode: TsExprNode;
|
|
FIdentifiers: TsExprIdentifierDefs;
|
|
FHashList: TFPHashObjectlist;
|
|
FDirty: Boolean;
|
|
FWorksheet: TsBasicWorksheet;
|
|
FDialect: TsFormulaDialect;
|
|
FSourceCell: PCell;
|
|
FDestCell: PCell;
|
|
FListSep: Char;
|
|
procedure CheckEOF;
|
|
function GetAsBoolean: Boolean;
|
|
function GetAsDateTime: TDateTime;
|
|
function GetAsFloat: TsExprFloat;
|
|
function GetAsInteger: Int64;
|
|
function GetAsString: String;
|
|
function GetDecimalSeparator: Char;
|
|
function GetExpression(ADialect: TsFormulaDialect): String;
|
|
function GetFormatSettings: TFormatSettings;
|
|
function GetR1C1Expression(ACell: PCell): String;
|
|
function GetRPNFormula: TsRPNFormula;
|
|
procedure SetBuiltIns(const AValue: TsBuiltInExprCategories);
|
|
procedure SetDialect(const AValue: TsFormulaDialect);
|
|
procedure SetExpression(ADialect: TsFormulaDialect; const AValue: String);
|
|
procedure SetIdentifiers(const AValue: TsExprIdentifierDefs);
|
|
procedure SetR1C1Expression(ACell: PCell; const AValue: String);
|
|
procedure SetRPNFormula(const AFormula: TsRPNFormula);
|
|
|
|
protected
|
|
FFormatSettings: PFormatSettings;
|
|
FContains3DRef: Boolean;
|
|
class function BuiltinExpressionManager: TsBuiltInExpressionManager;
|
|
function BuildStringFormula: String;
|
|
procedure ParserError(Msg: String);
|
|
//function GetLocalizedExpression: String; virtual;
|
|
procedure InternalSetExpression(ADialect: TsFormulaDialect; const AValue: String);
|
|
// procedure SetLocalizedExpression(const AValue: String); virtual;
|
|
procedure UpdateExprFormatSettings;
|
|
|
|
procedure CheckResultType(const Res: TsExpressionResult;
|
|
AType: TsResultType); inline;
|
|
function CurrentToken: String;
|
|
function CurrentOrEOFToken: String;
|
|
function GetToken: TsTokenType;
|
|
function Level1: TsExprNode;
|
|
function Level2: TsExprNode;
|
|
function Level3: TsExprNode;
|
|
function Level4: TsExprNode;
|
|
function Level5: TsExprNode;
|
|
function Level6: TsExprNode;
|
|
function Level7: TsExprNode;
|
|
function Primitive: TsExprNode;
|
|
function TokenType: TsTokenType;
|
|
procedure CreateHashList;
|
|
property Scanner: TsExpressionScanner read FScanner;
|
|
property Dirty: Boolean read FDirty;
|
|
property ExprNode: TsExprNode read FExprNode;
|
|
|
|
public
|
|
constructor Create(AWorksheet: TsBasicWorksheet); virtual;
|
|
destructor Destroy; override;
|
|
function IdentifierByName(AName: ShortString): TsExprIdentifierDef; virtual;
|
|
procedure Clear;
|
|
function CopyMode: Boolean;
|
|
function Evaluate: TsExpressionResult;
|
|
procedure EvaluateExpression(out AResult: TsExpressionResult);
|
|
function Has3DLinks: Boolean;
|
|
function IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer): boolean;
|
|
procedure PrepareCopyMode(ASourceCell, ADestCell: PCell);
|
|
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[ADialect: TsFormulaDialect]: String
|
|
read GetExpression write SetExpression;
|
|
{
|
|
property LocalizedExpression: String
|
|
read GetLocalizedExpression write SetLocalizedExpression;
|
|
}
|
|
property R1C1Expression[ACell: PCell]: String
|
|
read GetR1C1Expression write SetR1C1Expression;
|
|
property RPNFormula: TsRPNFormula
|
|
read GetRPNFormula write SetRPNFormula;
|
|
|
|
property DecimalSeparator: Char read GetDecimalSeparator;
|
|
property ListSeparator: Char read FListSep;
|
|
property FormatSettings: TFormatSettings read GetFormatSettings;
|
|
property Identifiers: TsExprIdentifierDefs read FIdentifiers write SetIdentifiers;
|
|
property BuiltIns: TsBuiltInExprCategories read FBuiltIns write SetBuiltIns;
|
|
property Worksheet: TsBasicWorksheet read FWorksheet;
|
|
property Dialect: TsFormulaDialect read FDialect write SetDialect;
|
|
property Contains3DRef: boolean read FContains3DRef;
|
|
|
|
end;
|
|
|
|
TsSpreadsheetParser = class(TsExpressionParser)
|
|
public
|
|
constructor Create(AWorksheet: TsBasicWorksheet); 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;
|
|
|
|
{ TsFormula }
|
|
TsFormula = record
|
|
Row, Col: Cardinal;
|
|
Text: String;
|
|
Parser: TsExpressionParser;
|
|
CalcState: TsCalcState;
|
|
end;
|
|
PsFormula = ^TsFormula;
|
|
|
|
{ Exception classes }
|
|
EExprParser = class(EGeneralExprParserError);
|
|
ECalcEngine = class(EGeneralExprParserError);
|
|
|
|
function TokenName(AToken: TsTokenType): String;
|
|
function ResultTypeName(AResult: TsResultType): String;
|
|
function CharToResultType(C: Char): TsResultType;
|
|
function BuiltinIdentifiers: 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; out AError: TsErrorValue);
|
|
function BooleanResult(AValue: Boolean): TsExpressionResult;
|
|
function CellResult(AValue: String): TsExpressionResult; overload;
|
|
function CellResult(ACellRow, ACellCol: Cardinal): TsExpressionResult; overload;
|
|
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 IsBlank(const AValue: TsExpressionResult): Boolean;
|
|
function IsInteger(const AValue: TsExpressionResult): Boolean;
|
|
function IsString(const AValue: TsExpressionResult): Boolean;
|
|
function StringResult(const AValue: String): TsExpressionResult;
|
|
|
|
procedure RegisterFunction(const AName: ShortString; const AResultType: Char;
|
|
const AParamTypes: String; const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack); overload;
|
|
procedure RegisterFunction(const AName: ShortString; const AResultType: Char;
|
|
const AParamTypes: String; const AExcelCode: Integer; ACallBack: TsExprFunctionEvent); overload;
|
|
|
|
function ConvertFormulaDialect(AFormula: String;
|
|
ASrcDialect, ADestDialect: TsFormulaDialect; AWorksheet: TsBasicWorksheet): String;
|
|
|
|
var
|
|
// Format settings used in stored parsed formulas.
|
|
ExprFormatSettings: TFormatSettings;
|
|
|
|
const
|
|
HYPERLINK_SEPARATOR = '|#@#|'; // Separats link and caption parts of a hyperlink
|
|
|
|
const
|
|
AllBuiltIns = [bcMath, bcStatistics, bcStrings, bcLogical, bcDateTime, bcLookup,
|
|
bcInfo, bcUser];
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
typinfo, math, lazutf8, dateutils,
|
|
fpsutils, fpsfunc, fpsStrings, fpspreadsheet;
|
|
|
|
const
|
|
cNull = #0;
|
|
cDoubleQuote = '"';
|
|
cError = '#';
|
|
|
|
Digits = ['0'..'9']; // + decimalseparator
|
|
WhiteSpace = [' ', #13, #10, #9];
|
|
Operators = ['+', '-', '<', '>', '=', '/', '*', '&', '%', '^'];
|
|
Delimiters = Operators + ['(', ')']; // + listseparator
|
|
Symbols = Delimiters;
|
|
WordDelimiters = WhiteSpace + Symbols;
|
|
|
|
|
|
{ ---------------------------------------------------------------------
|
|
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(rsInvalidResultCharacter, [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(AParser: TsExpressionParser);
|
|
begin
|
|
Source := '';
|
|
FParser := AParser;
|
|
FSheetnameTerminator := '!';
|
|
FSavedSheetNameTerminator := '!';
|
|
end;
|
|
|
|
{ Scans until closing square bracket is reached. In OpenDocument, this is
|
|
a cell or cell range identifier.
|
|
It has the structure [sheet1.C1R1:sheet2.C2R2] }
|
|
function TsExpressionScanner.DoCellRangeODS: TsTokenType;
|
|
type
|
|
TScannerStateODS = (ssInSheet1, ssInCol1, ssInRow1, ssInSheet2, ssInCol2, ssInRow2);
|
|
var
|
|
C: Char;
|
|
prevC: Char;
|
|
state: TScannerStateODS;
|
|
val: Integer;
|
|
isQuotedSheetName: Boolean;
|
|
begin
|
|
FSheet1 := '';
|
|
FSheet2 := '';
|
|
FCellRange.Row1 := Cardinal(-1);
|
|
FCellRange.Col1 := Cardinal(-1);
|
|
FCellRange.Row2 := Cardinal(-1);
|
|
FCellRange.Col2 := Cardinal(-1);
|
|
FFlags := rfAllRel;
|
|
|
|
isQuotedSheetName := false;
|
|
state := ssInSheet1;
|
|
FToken := '';
|
|
C := NextPos;
|
|
prevC := #0;
|
|
while (C <> ']') and (C <> cNULL) do begin
|
|
case C of
|
|
cNULL: ScanError(rsUnexpectedEndOfExpression);
|
|
'.': begin
|
|
if (state = ssInSheet1) then
|
|
begin
|
|
FSheet1 := FToken;
|
|
state := ssInCol1;
|
|
end else
|
|
if (state = ssInSheet2) then
|
|
begin
|
|
FSheet2 := FToken;
|
|
state := ssInCol2;
|
|
end else
|
|
ScanError(rsIllegalODSCellRange);
|
|
FToken := '';
|
|
val := 0;
|
|
end;
|
|
':': if (state = ssInRow1) then
|
|
begin
|
|
FCellRange.Row1 := val-1;
|
|
state := ssInSheet2;
|
|
FToken := '';
|
|
end else
|
|
ScanError(rsIllegalODSCellRange);
|
|
'$': if (prevC in [#0, ':']) then
|
|
isQuotedSheetName := true
|
|
else
|
|
case state of
|
|
ssInCol1: if prevC = '.' then Exclude(FFlags, rfRelCol) else Exclude(FFlags, rfRelRow);
|
|
ssInCol2: if prevC = '.' then Exclude(FFlags, rfRelCol2) else Exclude(FFlags, rfRelRow2);
|
|
end;
|
|
'''': if isQuotedSheetName or (prevC in [#0, ':']) then begin
|
|
C := NextPos;
|
|
FToken := '';
|
|
while C <> '''' do begin
|
|
FToken := FToken + C;
|
|
C := NextPos;
|
|
end;
|
|
isQuotedSheetName := false;
|
|
end;
|
|
else
|
|
if (state in [ssInSheet1, ssInSheet2]) then
|
|
FToken := FToken + C
|
|
else
|
|
case C of
|
|
'A'..'Z':
|
|
val := val*10 + ord(C) - ord('A');
|
|
'a'..'z':
|
|
val := val*10 + ord(C) - ord('a');
|
|
'0'..'9':
|
|
if state = ssInCol1 then begin
|
|
FCellRange.Col1 := val;
|
|
val := (ord(C) - ord('0'));
|
|
state := ssInRow1;
|
|
end else
|
|
if state = ssInRow1 then
|
|
val := val*10 + (ord(C) - ord('0'))
|
|
else
|
|
if state = ssInCol2 then begin
|
|
FCellRange.Col2 := val;
|
|
val := (ord(C) - ord('0'));
|
|
state := ssInRow2;
|
|
end else
|
|
if state = ssInRow2 then
|
|
val := val*10 + (ord(C) - ord('0'));
|
|
end;
|
|
end;
|
|
prevC := C;
|
|
C := NextPos;
|
|
end;
|
|
if C <> ']' then
|
|
ScanError(Format(rsRightSquareBracketExpected, [FPos, C]));
|
|
case state of
|
|
ssInRow1:
|
|
if val > 0 then FCellRange.Row1 := val - 1 else ScanError(rsIllegalODSCellRange);
|
|
ssInRow2:
|
|
if val > 0 then FCellRange.Row2 := val - 1 else ScanError(rsIllegalODSCellRange);
|
|
end;
|
|
if FCellRange.Col2 = Cardinal(-1) then Exclude(FFlags, rfRelCol2);
|
|
if FCellRange.Row2 = Cardinal(-1) then Exclude(FFlags, rfRelRow2);
|
|
C := NextPos;
|
|
Result := ttSpreadsheetAddress;
|
|
FTokenType := Result;
|
|
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
|
|
if D = FParser.ListSeparator then
|
|
Result := ttListSep
|
|
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(rsUnknownDelimiter, [D]));
|
|
end;
|
|
end;
|
|
|
|
function TsExpressionScanner.DoError: TsTokenType;
|
|
var
|
|
C: Char;
|
|
begin
|
|
C := CurrentChar;
|
|
while (C in ['A', 'D', 'E', 'F', 'I', 'L', 'M', 'N', 'O', 'R', 'U', 'V', '0', '!', '?', '/', '#']) do
|
|
// while (C in ['D','I','V','/','0', 'N', 'U', 'L', 'V', 'A', 'E', 'R', 'F', 'M', '!', '?']) do
|
|
// while ((not IsWordDelim(C) or (C in ['/', '0', '!', '?'])) and (C <> cNull) do
|
|
begin
|
|
FToken := FToken + C;
|
|
C := NextPos;
|
|
end;
|
|
Result := ttError;
|
|
end;
|
|
|
|
function TsExpressionScanner.DoIdentifier: TsTokenType;
|
|
var
|
|
isInSqBr: Boolean;
|
|
isQuoted: Boolean;
|
|
|
|
function IsR1C1Char(C: Char): Boolean; inline;
|
|
begin
|
|
if (FParser.Dialect = fdExcelR1C1) then
|
|
Result := (C = '[') or (C = ']') or (isInSqBr and (C = '-'))
|
|
else
|
|
Result := false;
|
|
end;
|
|
|
|
var
|
|
C: Char;
|
|
S: String;
|
|
ok: Boolean;
|
|
baseCol, baseRow: Cardinal;
|
|
begin
|
|
C := CurrentChar;
|
|
isQuoted := C = '''';
|
|
isInSqBr := C = '[';
|
|
|
|
while ((not IsWordDelim(C)) or IsQuoted or IsR1C1Char(C)) and (C <> cNULL) do
|
|
begin
|
|
FToken := FToken + C;
|
|
C := NextPos;
|
|
if C = '''' then isQuoted := false;
|
|
if (FParser.Dialect = fdExcelR1C1) then begin
|
|
if (C = '[') then
|
|
isInSqBr := true
|
|
else if (C = ']') then
|
|
isInSqBr := false;
|
|
end;
|
|
end;
|
|
|
|
if (FParser.Dialect = fdExcelR1C1) then begin
|
|
if FParser.FDestCell = nil then begin
|
|
baseRow := 0;
|
|
baseCol := 0;
|
|
end else
|
|
begin
|
|
baseRow := FParser.FDestCell^.Row;
|
|
baseCol := FParser.FDestCell^.Col;
|
|
end;
|
|
ok := ParseCellRangeString_R1C1(FToken,
|
|
baseRow, baseCol,
|
|
FSheet1, FSheet2,
|
|
FCellRange.Row1, FCellRange.Col1, FCellRange.Row2, FCellRange.Col2,
|
|
FFlags)
|
|
end else begin
|
|
ok := ParseCellRangeString(FToken,
|
|
FSheet1, FSheet2,
|
|
FCellRange.Row1, FCellRange.Col1, FCellRange.Row2, FCellRange.Col2,
|
|
FFlags);
|
|
end;
|
|
|
|
if ok and (C <> '(') then
|
|
begin
|
|
Result := ttSpreadsheetAddress;
|
|
exit;
|
|
end;
|
|
|
|
S := LowerCase(FToken);
|
|
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') or (C = FParser.DecimalSeparator)) 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(rsInvalidNumberChar, [C]));
|
|
FToken := FToken+C;
|
|
prevC := Upcase(C);
|
|
C := NextPos;
|
|
end;
|
|
if not TryStrToFloat(FToken, X, FParser.FFormatSettings^) then
|
|
ScanError(Format(rsInvalidNumber, [FToken]));
|
|
Result := ttNumber;
|
|
end;
|
|
|
|
{ Scans until closing square bracket is reached. In OpenDocument, this is
|
|
a cell or cell range identifier.
|
|
It has the structure [sheet1.C1R1:sheet2.C2R2] }
|
|
procedure TsExpressionScanner.GetCellRangeParamsODS(
|
|
out ASheet1, ASheet2: String; out ARow1, ACol1, ARow2, ACol2: Cardinal;
|
|
out AFlags: TsRelFlags);
|
|
type
|
|
TScannerStateODS = (ssSheet1, ssCol1, ssRow1, ssSheet2, ssCol2, ssRow2);
|
|
var
|
|
C: Char;
|
|
prevC: Char;
|
|
state: TScannerStateODS;
|
|
val: Integer;
|
|
begin
|
|
ASheet1 := '';
|
|
ASheet2 := '';
|
|
ARow1 := Cardinal(-1);
|
|
ACol1 := Cardinal(-1);
|
|
ARow2 := Cardinal(-1);
|
|
ACol2 := Cardinal(-1);
|
|
AFlags := rfAllRel;
|
|
|
|
state := ssSheet1;
|
|
FToken := '';
|
|
C := NextPos;
|
|
prevC := #0;
|
|
while (C <> ']') and (C <> cNULL) do begin
|
|
case C of
|
|
cNULL: ScanError(rsUnexpectedEndOfExpression);
|
|
'.': begin
|
|
if (state = ssSheet1) then
|
|
begin
|
|
ASheet1 := FToken;
|
|
state := ssCol1;
|
|
end else
|
|
if (state = ssSheet2) then
|
|
begin
|
|
ASheet2 := FToken;
|
|
state := ssCol2;
|
|
end else
|
|
ScanError(rsIllegalODSCellRange);
|
|
FToken := '';
|
|
val := 0;
|
|
end;
|
|
':': if (state = ssRow1) then
|
|
begin
|
|
ARow1 := val-1;
|
|
state := ssSheet2;
|
|
FToken := '';
|
|
end else
|
|
ScanError(rsIllegalODSCellRange);
|
|
'$': case state of
|
|
ssCol1: if prevC = '.' then Exclude(AFlags, rfRelCol) else Exclude(AFlags, rfRelRow);
|
|
ssCol2: if prevC = '.' then Exclude(AFlags, rfRelCol2) else Exclude(AFlags, rfRelRow2);
|
|
end;
|
|
else
|
|
if (state in [ssSheet1, ssSheet2]) then
|
|
FToken := FToken + C
|
|
else
|
|
case C of
|
|
'A'..'Z':
|
|
val := val*10 + ord(C) - ord('A');
|
|
'a'..'z':
|
|
val := val*10 + ord(C) - ord('a');
|
|
'0'..'9':
|
|
if state = ssCol1 then begin
|
|
ACol1 := val;
|
|
val := ord(C) - ord('0');
|
|
state := ssRow1;
|
|
end else
|
|
if state = ssCol2 then begin
|
|
ACol2 := val;
|
|
val := ord(C) - ord('0');
|
|
state := ssRow2;
|
|
end;
|
|
end;
|
|
end;
|
|
prevC := C;
|
|
C := NextPos;
|
|
end;
|
|
if C <> ']' then
|
|
ScanError(Format(rsRightSquareBracketExpected, [FPos, C]));
|
|
case state of
|
|
ssRow1:
|
|
if val > 0 then ARow1 := val - 1 else ScanError(rsIllegalODSCellRange);
|
|
ssRow2:
|
|
if val > 0 then ARow2 := val - 1 else ScanError(rsIllegalODSCellRange);
|
|
end;
|
|
if ACol2 = Cardinal(-1) then Exclude(AFlags, rfRelCol2);
|
|
if ARow2 = Cardinal(-1) then Exclude(AFlags, rfRelRow2);
|
|
C := NextPos;
|
|
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(rsBadQuotes);
|
|
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 = '[') then
|
|
Result := DoCellRangeODS
|
|
else 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 (C = cError) then
|
|
Result := DoError
|
|
else if IsAlpha(C) or (C = '$') or (C = '''') then
|
|
Result := DoIdentifier
|
|
else
|
|
ScanError(Format(rsUnknownCharacter, [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) or (C = FParser.ListSeparator);
|
|
end;
|
|
|
|
function TsExpressionScanner.IsDigit(C: Char): Boolean;
|
|
begin
|
|
Result := (C in Digits) or (C = FParser.DecimalSeparator);
|
|
end;
|
|
|
|
function TsExpressionScanner.IsWordDelim(C: Char): Boolean;
|
|
begin
|
|
Result := (C in WordDelimiters) or (C = FParser.ListSeparator);
|
|
end;
|
|
(*
|
|
function TsExpressionScanner.IsWordDelimR1C1(C: Char): boolean;
|
|
begin
|
|
Result := not (C in (['[', ']'] + Digits)
|
|
Result := (C in (WordDelimiters + ['[', ']'])) or (C = FParser.ListSeparator);
|
|
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: TsBasicWorksheet);
|
|
begin
|
|
inherited Create;
|
|
FWorksheet := AWorksheet;
|
|
FIdentifiers := TsExprIdentifierDefs.Create(TsExprIdentifierDef);
|
|
FIdentifiers.FParser := Self;
|
|
FScanner := TsExpressionScanner.Create(self);
|
|
FHashList := TFPHashObjectList.Create(False);
|
|
|
|
// Prepare for ExcelA1 dialect which is the default dialect. Can't call
|
|
// SetDialect(fdExcelA1) because it exits immediately at default dialect.
|
|
FDialect := fdExcelA1;
|
|
FListSep := ',';
|
|
FFormatSettings := @ExprFormatSettings;
|
|
UpdateExprFormatSettings;
|
|
end;
|
|
|
|
destructor TsExpressionParser.Destroy;
|
|
begin
|
|
FreeAndNil(FHashList);
|
|
FreeAndNil(FExprNode);
|
|
FreeAndNil(FIdentifiers);
|
|
FreeAndNil(FScanner);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ Constructs the string formula from the tree of expression nodes. Gets the
|
|
decimal and list separator from current formatsettings. }
|
|
function TsExpressionParser.BuildStringFormula: String;
|
|
begin
|
|
// ExprFormatSettings := AFormatSettings;
|
|
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(rsUnexpectedEndOfExpression);
|
|
end;
|
|
|
|
procedure TsExpressionParser.CheckResultType(const Res: TsExpressionResult;
|
|
AType: TsResultType); inline;
|
|
begin
|
|
if (Res.ResultType <> AType) then
|
|
RaiseParserError(rsInvalidResultType, [ResultTypeName(Res.ResultType)]);
|
|
end;
|
|
|
|
procedure TsExpressionParser.Clear;
|
|
begin
|
|
FExpression := '';
|
|
FHashList.Clear;
|
|
FreeAndNil(FExprNode);
|
|
end;
|
|
|
|
{ Prepares copy mode: The formula is contained in ASourceCell and will be
|
|
modified such as seen from ADestCell. }
|
|
procedure TsExpressionParser.PrepareCopyMode(ASourceCell, ADestCell: PCell);
|
|
begin
|
|
FSourceCell := ASourceCell;
|
|
FDestCell := ADestCell;
|
|
end;
|
|
|
|
{ Signals that the parser is in "CopyMode", i.e. there is are source and
|
|
destination cells. All relative references in the formula of the source cell
|
|
habe to be adapted as seen from the destination cell. }
|
|
function TsExpressionParser.CopyMode: Boolean;
|
|
begin
|
|
Result := (FDestCell <> nil) and (FSourceCell <> nil);
|
|
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.CurrentOrEOFToken: String;
|
|
begin
|
|
if (FScanner.TokenType = ttEOF) or (FScanner.Token = '') then
|
|
Result := 'end of formula'
|
|
else
|
|
Result := FScanner.Token;
|
|
end;
|
|
|
|
function TsExpressionParser.Evaluate: TsExpressionResult;
|
|
begin
|
|
EvaluateExpression(Result);
|
|
end;
|
|
|
|
procedure TsExpressionParser.EvaluateExpression(out AResult: TsExpressionResult);
|
|
var
|
|
fs: TFormatSettings;
|
|
begin
|
|
{ // Not needed. May be missing after copying formulas
|
|
if (FExpression = '') then
|
|
ParserError(rsExpressionEmpty);
|
|
}
|
|
if not Assigned(FExprNode) then
|
|
ParserError(rsErrorInExpression);
|
|
fs := ExprFormatSettings;
|
|
try
|
|
UpdateExprFormatSettings;
|
|
// ExprFormatSettings := FFormatSettings;
|
|
// FFormatSettings := ExprFormatSettings;
|
|
FExprNode.GetNodeValue(AResult);
|
|
finally
|
|
ExprFormatSettings := fs;
|
|
end;
|
|
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;
|
|
begin
|
|
if FDirty then
|
|
CreateHashList;
|
|
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
|
|
|
|
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;
|
|
case tt of
|
|
ttLessthan : C := TsLessExprNode;
|
|
ttLessthanEqual : C := TsLessEqualExprNode;
|
|
ttLargerThan : C := TsGreaterExprNode;
|
|
ttLargerThanEqual : C := TsGreaterEqualExprNode;
|
|
ttEqual : C := TsEqualExprNode;
|
|
ttNotEqual : C := TsNotEqualExprNode;
|
|
else
|
|
ParserError(rsUnknownComparison)
|
|
end;
|
|
Result := C.Create(self, 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;
|
|
case tt of
|
|
ttPlus : Result := TsAddExprNode.Create(self, Result, right);
|
|
ttMinus : Result := TsSubtractExprNode.Create(self, Result, right);
|
|
ttConcat: Result := TsConcatExprNode.Create(self, 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;
|
|
case tt of
|
|
ttMul : Result := TsMultiplyExprNode.Create(self, Result, right);
|
|
ttDiv : Result := TsDivideExprNode.Create(self, Result, right);
|
|
end;
|
|
end;
|
|
except
|
|
Result.Free;
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
function TsExpressionParser.Level5: TsExprNode;
|
|
var
|
|
right: TsExprNode;
|
|
begin
|
|
{$ifdef debugexpr} Writeln('Level 5 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
|
|
Result := Level6;
|
|
try
|
|
while (TokenType = ttPower) do
|
|
begin
|
|
GetToken;
|
|
right := Level6;
|
|
Result := TsPowerExprNode.Create(self, Result, right);
|
|
end;
|
|
except
|
|
Result.Free;
|
|
Raise;
|
|
end;
|
|
end;
|
|
|
|
function TsExpressionParser.Level6: TsExprNode;
|
|
var
|
|
signs: String;
|
|
i: Integer;
|
|
begin
|
|
{$ifdef debugexpr} Writeln('Level 6 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
|
|
signs := '';
|
|
while (TokenType in [ttPlus, ttMinus]) do
|
|
begin
|
|
case TokenType of
|
|
ttPlus : signs := signs + '+';
|
|
ttMinus : signs := signs + '-';
|
|
end;
|
|
GetToken;
|
|
end;
|
|
Result := Level7;
|
|
i := Length(signs);
|
|
while (i > 0) do begin
|
|
case signs[i] of
|
|
'+': Result := TsUPlusExprNode.Create(self, Result);
|
|
'-': Result := TsUMinusExprNode.Create(self, Result);
|
|
end;
|
|
dec(i);
|
|
end;
|
|
|
|
while TokenType = ttPercent do begin
|
|
Result := TsPercentExprNode.Create(self, Result);
|
|
GetToken;
|
|
end;
|
|
end;
|
|
|
|
function TsExpressionParser.Level7: TsExprNode;
|
|
var
|
|
currToken: String;
|
|
begin
|
|
{$ifdef debugexpr} Writeln('Level 7 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
|
|
if (TokenType = ttLeft) then
|
|
begin
|
|
GetToken;
|
|
Result := TsParenthesisExprNode.Create(self, Level1);
|
|
try
|
|
if (TokenType <> ttRight) then begin
|
|
currToken := CurrentToken;
|
|
if TokenType = ttEOF then currToken := 'end of formula';
|
|
ParserError(Format(rsRightBracketExpected, [SCanner.Pos, currToken]));
|
|
end;
|
|
GetToken;
|
|
except
|
|
Result.Free;
|
|
raise;
|
|
end;
|
|
end
|
|
else
|
|
Result := Primitive;
|
|
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 = nil;
|
|
AI: Integer;
|
|
optional: Boolean;
|
|
token: String;
|
|
prevTokenType: TsTokenType;
|
|
sheetname1, sheetname2: String;
|
|
rng: TsCellRange;
|
|
flags: TsRelFlags;
|
|
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(self, I)
|
|
else
|
|
if TryStrToFloat(CurrentToken, X, FFormatSettings^) then
|
|
Result := TsConstExprNode.CreateFloat(self, X)
|
|
else
|
|
ParserError(Format(rsInvalidFloat, [CurrentToken]));
|
|
end
|
|
else if (TokenType = ttTrue) then
|
|
Result := TsConstExprNode.CreateBoolean(self, true)
|
|
else if (TokenType = ttFalse) then
|
|
Result := TsConstExprNode.CreateBoolean(self, false)
|
|
else if (TokenType = ttString) then
|
|
Result := TsConstExprNode.CreateString(self, CurrentToken)
|
|
else if (TokenType = ttSpreadsheetAddress) then
|
|
begin
|
|
sheetname1 := FScanner.TokenSheet1;
|
|
sheetname2 := FScanner.TokenSheet2;
|
|
rng := FScanner.TokenCellRange;
|
|
flags := FScanner.TokenFlags;
|
|
if (sheetname2 = '') and
|
|
(rng.Row2 = Cardinal(-1)) and (rng.Col2 = Cardinal(-1))
|
|
then
|
|
Result := TsCellExprNode.Create(self, FWorksheet, sheetname1, rng.Row1, rng.Col1, flags)
|
|
else
|
|
Result := TsCellRangeExprNode.Create(self, FWorksheet, sheetname1, sheetname2, rng, flags)
|
|
end
|
|
else if (TokenType = ttError) then
|
|
Result := TsConstExprNode.CreateError(self, CurrentToken)
|
|
else if not (TokenType in [ttIdentifier]) then
|
|
ParserError(Format(rsUnknownTokenAtPos, [Scanner.Pos, CurrentToken]))
|
|
else
|
|
begin
|
|
token := Uppercase(CurrentToken);
|
|
ID := self.IdentifierByName(token);
|
|
if (ID = nil) then
|
|
ParserError(Format(rsUnknownIdentifier, [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(rsLeftBracketExpected, [Scanner.Pos, CurrentOrEOFToken]));
|
|
GetToken;
|
|
if (TokenType <> ttRight) then
|
|
ParserError(Format(rsRightBracketExpected, [Scanner.Pos, CurrentOrEOFToken]));
|
|
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(rsLeftBracketExpected, [Scanner.Pos, CurrentOrEofToken]));
|
|
SetLength(Args, abs(lCount));
|
|
AI := 0;
|
|
try
|
|
repeat
|
|
prevTokenType := TokenType;
|
|
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;
|
|
if (prevTokenType in [ttLeft, ttListSep]) and (TokenType in [ttListSep, ttRight]) then
|
|
begin
|
|
Args[AI] := TsMissingArgExprNode.Create;
|
|
inc(AI);
|
|
Continue;
|
|
end;
|
|
Args[AI] := Level1;
|
|
inc(AI);
|
|
optional := ID.IsOptionalArgument(AI+1);
|
|
if not optional then
|
|
begin
|
|
if (TokenType <> ttListSep) then
|
|
if (AI < abs(lCount)) then
|
|
ParserError(Format(rsCommaExpected, [Scanner.Pos, CurrentOrEofToken]))
|
|
end;
|
|
until (AI = lCount) or (((lCount < 0) or optional) and (TokenType = ttRight));
|
|
if TokenType <> ttRight then
|
|
ParserError(Format(rsRightBracketExpected, [Scanner.Pos, CurrentOrEofToken]));
|
|
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(self, ID);
|
|
itFunctionCallBack:
|
|
Result := TsFunctionCallBackExprNode.CreateFunction(self, ID, Args);
|
|
itFunctionHandler:
|
|
Result := TFPFunctionEventHandlerExprNode.CreateFunction(self, ID, Args);
|
|
end;
|
|
end;
|
|
GetToken;
|
|
end;
|
|
|
|
function TsExpressionParser.ResultType: TsResultType;
|
|
begin
|
|
if not Assigned(FExprNode) then
|
|
ParserError(rsErrorInExpression);
|
|
Result := FExprNode.NodeType;;
|
|
end;
|
|
|
|
procedure TsExpressionParser.SetBuiltIns(const AValue: TsBuiltInExprCategories);
|
|
begin
|
|
if FBuiltIns = AValue then
|
|
exit;
|
|
FBuiltIns := AValue;
|
|
FDirty := true;
|
|
end;
|
|
|
|
function TsExpressionParser.GetDecimalSeparator: Char;
|
|
begin
|
|
Result := FFormatSettings^.DecimalSeparator;
|
|
end;
|
|
|
|
{ Builds an expression string for the currently loaded parser tree. The string
|
|
is created for the specified formula dialect. The formula dialect used by
|
|
the parser is restored afterwards. }
|
|
function TsExpressionParser.GetExpression(ADialect: TsFormulaDialect): String;
|
|
var
|
|
oldDialect: TsFormulaDialect;
|
|
begin
|
|
if ADialect = fdExcelR1C1 then
|
|
raise Exception.Create('Please use R1C1Expression');
|
|
|
|
oldDialect := FDialect;
|
|
try
|
|
SetDialect(ADialect);
|
|
Result := BuildStringFormula;
|
|
finally
|
|
SetDialect(oldDialect);
|
|
end;
|
|
end;
|
|
|
|
function TsExpressionParser.GetFormatSettings: TFormatSettings;
|
|
begin
|
|
Result := FFormatSettings^;
|
|
end;
|
|
|
|
{ Builds an expression string for the currently loaded parser tree. The string
|
|
is created for Excel's R1C1 notation. ACell points to the cell to which cell
|
|
references are relative. The formula dialect used by the parser is
|
|
restored afterwards. }
|
|
function TsExpressionParser.GetR1C1Expression(ACell: PCell): String;
|
|
var
|
|
oldDialect: TsFormulaDialect;
|
|
begin
|
|
oldDialect := FDialect;
|
|
try
|
|
SetDialect(fdExcelR1C1);
|
|
PrepareCopyMode(ACell, ACell);
|
|
Result := BuildStringFormula;
|
|
finally
|
|
PrepareCopyMode(nil, nil);
|
|
SetDialect(oldDialect);
|
|
end;
|
|
end;
|
|
|
|
{
|
|
function TsExpressionParser.GetLocalizedExpression: String;
|
|
begin
|
|
SetDialect(fdLocalized);
|
|
Result := BuildStringFormula;
|
|
end;
|
|
}
|
|
function TsExpressionParser.Has3DLinks: Boolean;
|
|
begin
|
|
Result := FExprNode.Has3DLink;
|
|
end;
|
|
|
|
function TsExpressionParser.IterateNodes(AProc: TsExprNodeProc;
|
|
AData1, AData2: Pointer): Boolean;
|
|
begin
|
|
Result := false;
|
|
FExprNode.IterateNodes(AProc, AData1, AData2, Result);
|
|
end;
|
|
|
|
procedure TsExpressionParser.SetDialect(const AValue: TsFormulaDialect);
|
|
begin
|
|
if FDialect = AValue then
|
|
exit;
|
|
|
|
FDialect := AValue;
|
|
case FDialect of
|
|
fdExcelA1,
|
|
fdExcelR1C1:
|
|
begin
|
|
FListSep := ',';
|
|
FFormatSettings := @ExprFormatSettings;
|
|
UpdateExprFormatSettings;
|
|
end;
|
|
fdOpenDocument:
|
|
begin
|
|
FListSep := ';';
|
|
FFormatSettings := @ExprFormatSettings;
|
|
UpdateExprFormatSettings;
|
|
end;
|
|
fdLocalized:
|
|
begin
|
|
FFormatSettings := @TsWorksheet(FWorksheet).Workbook.FormatSettings;
|
|
FListSep := FFormatSettings^.ListSeparator;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsExpressionParser.InternalSetExpression(ADialect: TsFormulaDialect;
|
|
const AValue: String);
|
|
begin
|
|
if FExpression = AValue then
|
|
exit;
|
|
|
|
FExpression := AValue;
|
|
if (AValue <> '') and (AValue[1] = '=') then
|
|
Delete(FExpression, 1, 1);
|
|
|
|
SetDialect(ADialect);
|
|
|
|
FreeAndNil(FExprNode);
|
|
FScanner.Source := FExpression;
|
|
if FExpression <> '' then begin
|
|
GetToken;
|
|
FExprNode := Level1;
|
|
if TokenType <> ttEOF then
|
|
ParserError(Format(rsUnTerminatedExpression, [Scanner.Pos, CurrentToken]));
|
|
FExprNode.Check;
|
|
end;
|
|
end;
|
|
|
|
{ Makes the parser analyze the given expression string. The expression string
|
|
is assumed to be valid for the specified formula dialect. }
|
|
procedure TsExpressionParser.SetExpression(ADialect: TsFormulaDialect;
|
|
const AValue: String);
|
|
begin
|
|
if FDialect = fdExcelR1C1 then
|
|
raise Exception.Create('Please use R1C1Expression');
|
|
|
|
InternalSetExpression(ADialect, AValue);
|
|
end;
|
|
(*
|
|
{ Sets a localized Excel expression in A1 syntax. The format settings needed
|
|
for localization are taken from the workbook. }
|
|
procedure TsExpressionParser.SetLocalizedExpression(const AValue: String);
|
|
begin
|
|
InternalSetExpression(fdLocalized, AValue);
|
|
end; *)
|
|
|
|
procedure TsExpressionParser.SetIdentifiers(const AValue: TsExprIdentifierDefs);
|
|
begin
|
|
FIdentifiers.Assign(AValue)
|
|
end;
|
|
|
|
{ Parses an expression in which cell references are given in Excel's R1C1 notation
|
|
ACell is the cell to which the created expression will be relative. }
|
|
procedure TsExpressionParser.SetR1C1Expression(ACell: PCell; const AValue: String);
|
|
begin
|
|
PrepareCopyMode(ACell, ACell);
|
|
try
|
|
InternalSetExpression(fdExcelR1C1, AValue);
|
|
finally
|
|
PrepareCopyMode(nil, nil);
|
|
end;
|
|
end;
|
|
|
|
procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula);
|
|
|
|
procedure CreateNodeFromRPN(var ANode: TsExprNode; var AIndex: Integer);
|
|
var
|
|
left: TsExprNode = nil;
|
|
right: TsExprNode = nil;
|
|
operand: TsExprNode = nil;
|
|
fek: TFEKind;
|
|
r,c: Cardinal;
|
|
idx: Integer;
|
|
flags: TsRelFlags;
|
|
ID: TsExprIdentifierDef;
|
|
i, n: Integer;
|
|
args: TsExprArgumentArray = nil;
|
|
sn, sn2: string;
|
|
rng: TsCellRange;
|
|
begin
|
|
if AIndex < 0 then
|
|
exit;
|
|
|
|
fek := AFormula[AIndex].ElementKind;
|
|
|
|
case fek of
|
|
fekCell, fekCellRef:
|
|
begin
|
|
r := AFormula[AIndex].Row;
|
|
c := AFormula[AIndex].Col;
|
|
if (LongInt(r) < 0) or (LongInt(c) < 0) then
|
|
ANode := TsConstExprNode.CreateError(self, errIllegalRef)
|
|
else
|
|
begin
|
|
flags := AFormula[AIndex].RelFlags;
|
|
ANode := TsCellExprNode.Create(self, FWorksheet, '', r, c, flags);
|
|
end;
|
|
dec(AIndex);
|
|
end;
|
|
fekCell3D:
|
|
begin
|
|
idx := AFormula[AIndex].Sheet;
|
|
r := AFormula[AIndex].Row;
|
|
c := AFormula[AIndex].Col;
|
|
if (LongInt(r) < 0) or (LongInt(c) < 0) then
|
|
ANode := TsConstExprNode.CreateError(self, errIllegalRef)
|
|
else
|
|
begin
|
|
flags := AFormula[AIndex].RelFlags;
|
|
sn := (FWorksheet as TsWorksheet).Workbook.GetWorksheetByIndex(idx).Name;
|
|
ANode := TsCellExprNode.Create(Self, FWorksheet, sn, r, c, flags);
|
|
end;
|
|
dec(AIndex);
|
|
end;
|
|
fekCellRange, fekCellRange3D:
|
|
begin
|
|
rng.Row1 := AFormula[AIndex].Row;
|
|
rng.Col1 := AFormula[AIndex].Col;
|
|
rng.Row2 := AFormula[AIndex].Row2;
|
|
rng.Col2 := AFormula[AIndex].Col2;
|
|
flags := AFormula[AIndex].RelFlags;
|
|
if fek = fekCellRange then
|
|
ANode := TsCellRangeExprNode.Create(self, FWorksheet, '', '', rng, flags)
|
|
else begin
|
|
sn := (FWorksheet as TsWorksheet).Workbook.GetWorksheetByIndex(AFormula[AIndex].Sheet).Name;
|
|
if AFormula[AIndex].Sheet2 <> -1 then
|
|
sn2 := (FWorksheet as TsWorksheet).Workbook.GetWorksheetByIndex(AFormula[AIndex].Sheet2).Name
|
|
else
|
|
sn2 := '';
|
|
ANode := TsCellRangeExprNode.Create(self, FWorksheet, sn,sn2, rng, flags);
|
|
end;
|
|
dec(AIndex);
|
|
end;
|
|
fekNum:
|
|
begin
|
|
ANode := TsConstExprNode.CreateFloat(self, AFormula[AIndex].DoubleValue);
|
|
dec(AIndex);
|
|
end;
|
|
fekInteger:
|
|
begin
|
|
ANode := TsConstExprNode.CreateInteger(self, AFormula[AIndex].IntValue);
|
|
dec(AIndex);
|
|
end;
|
|
fekString:
|
|
begin
|
|
ANode := TsConstExprNode.CreateString(self, AFormula[AIndex].StringValue);
|
|
dec(AIndex);
|
|
end;
|
|
fekBool:
|
|
begin
|
|
ANode := TsConstExprNode.CreateBoolean(self, AFormula[AIndex].DoubleValue <> 0.0);
|
|
dec(AIndex);
|
|
end;
|
|
fekErr:
|
|
begin
|
|
ANode := TsConstExprNode.CreateError(self, TsErrorValue(AFormula[AIndex].IntValue));
|
|
dec(AIndex);
|
|
end;
|
|
fekMissingArg:
|
|
begin
|
|
ANode := TsMissingArgExprNode.Create;
|
|
dec(AIndex);
|
|
end;
|
|
|
|
// unary operations
|
|
fekPercent, fekUMinus, fekUPlus, fekParen:
|
|
begin
|
|
dec(AIndex);
|
|
CreateNodeFromRPN(operand, AIndex);
|
|
case fek of
|
|
fekPercent : ANode := TsPercentExprNode.Create(self, operand);
|
|
fekUMinus : ANode := TsUMinusExprNode.Create(self, operand);
|
|
fekUPlus : ANode := TsUPlusExprNode.Create(self, operand);
|
|
fekParen : ANode := TsParenthesisExprNode.Create(self, 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(self, left, right);
|
|
fekSub : ANode := TsSubtractExprNode.Create(self, left, right);
|
|
fekMul : ANode := TsMultiplyExprNode.Create(self, left, right);
|
|
fekDiv : ANode := TsDivideExprNode.Create(self, left, right);
|
|
fekPower : ANode := TsPowerExprNode.Create(self, left, right);
|
|
fekConcat : ANode := tsConcatExprNode.Create(self, left, right);
|
|
fekEqual : ANode := TsEqualExprNode.Create(self, left, right);
|
|
fekNotEqual : ANode := TsNotEqualExprNode.Create(self, left, right);
|
|
fekGreater : ANode := TsGreaterExprNode.Create(self, left, right);
|
|
fekGreaterEqual: ANode := TsGreaterEqualExprNode.Create(self, left, right);
|
|
fekLess : ANode := TsLessExprNode.Create(self, left, right);
|
|
fekLessEqual : ANode := tsLessEqualExprNode.Create(self, left, right);
|
|
end;
|
|
end;
|
|
|
|
// functions
|
|
fekFunc:
|
|
begin
|
|
ID := self.IdentifierByName(AFormula[AIndex].FuncName);
|
|
if ID = nil then
|
|
begin
|
|
ParserError(Format(rsUnknownIdentifier, [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(self, ID);
|
|
itFunctionCallBack:
|
|
ANode := TsFunctionCallBackExprNode.CreateFunction(self, ID, args);
|
|
itFunctionHandler:
|
|
ANode := TFPFunctionEventHandlerExprNode.CreateFunction(self, ID, args);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end; //case
|
|
end; //begin
|
|
|
|
var
|
|
index: Integer;
|
|
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;
|
|
|
|
procedure TsExpressionParser.UpdateExprFormatSettings;
|
|
var
|
|
book: TsWorkbook;
|
|
begin
|
|
book := TsWorksheet(FWorksheet).Workbook;
|
|
ExprFormatSettings.ShortDateFormat := book.FormatSettings.ShortDateFormat;
|
|
ExprFormatSettings.ShortTimeFormat := book.FormatSettings.ShortTimeFormat;
|
|
ExprFormatSettings.LongTimeFormat := book.FormatSettings.LongTimeFormat;
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
{ TsSpreadsheetParser }
|
|
{------------------------------------------------------------------------------}
|
|
|
|
constructor TsSpreadsheetParser.Create(AWorksheet: TsBasicWorksheet);
|
|
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(rsUnknownIdentifier, [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;
|
|
end;
|
|
|
|
procedure TsExprIdentifierDefs.SetI(AIndex: Integer;
|
|
const AValue: TsExprIdentifierDef);
|
|
begin
|
|
Items[AIndex] := AValue;
|
|
end;
|
|
|
|
procedure TsExprIdentifierDefs.Update(Item: TCollectionItem);
|
|
begin
|
|
Unused(Item);
|
|
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(rsInvalidResultType, [ResultTypeName(AType)])
|
|
end;
|
|
|
|
procedure TsExprIdentifierDef.CheckVariable;
|
|
begin
|
|
if Identifiertype <> itVariable then
|
|
RaiseParserError(rsNoVariable, [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.GetFormatSettings: TFormatSettings;
|
|
begin
|
|
Result := TsExprIdentifierDefs(Collection).Parser.FFormatSettings^;
|
|
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, GetFormatSettings);
|
|
rtDateTime : Result := FormatDateTime('cccc', FValue.ResDateTime, GetFormatSettings);
|
|
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(rsDuplicateIdentifier,[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, GetFormatSettings);
|
|
rtDateTime : FValue.ResDateTime := StrToDateTime(AValue, GetFormatSettings);
|
|
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.Check;
|
|
begin
|
|
end;
|
|
|
|
function TsExprNode.HasError(out AResult: TsExpressionResult): Boolean;
|
|
begin
|
|
GetNodeValue(AResult);
|
|
if AResult.ResultType = rtError then
|
|
begin
|
|
Result := true;
|
|
AResult := ErrorResult(AResult.ResError);
|
|
end else
|
|
Result := false;
|
|
end;
|
|
|
|
function TsExprNode.Has3DLink: Boolean;
|
|
begin
|
|
Result := false;
|
|
end;
|
|
|
|
procedure TsExprNode.IterateNodes(AProc: TsExprNodeProc; AData1, AData2: Pointer;
|
|
var MustRebuildFormulas: Boolean);
|
|
begin
|
|
Unused(AProc, AData1, AData2);
|
|
Unused(MustRebuildFormulas);
|
|
// to be overridden by descendant classes
|
|
end;
|
|
|
|
function TsExprNode.NodeValue: TsExpressionResult;
|
|
begin
|
|
GetNodeValue(Result);
|
|
end;
|
|
|
|
|
|
{ TsUnaryOperationExprNode }
|
|
|
|
constructor TsUnaryOperationExprNode.Create(AParser: TsExpressionParser;
|
|
AOperand: TsExprNode);
|
|
begin
|
|
FParser := AParser;
|
|
FOperand := AOperand;
|
|
end;
|
|
|
|
destructor TsUnaryOperationExprNode.Destroy;
|
|
begin
|
|
FreeAndNil(FOperand);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TsUnaryOperationExprNode.Check;
|
|
begin
|
|
if not Assigned(Operand) then
|
|
RaiseParserError(rsNoOperand, [Self.ClassName]);
|
|
end;
|
|
|
|
|
|
{ TsBinaryOperationExprNode }
|
|
|
|
constructor TsBinaryOperationExprNode.Create(AParser: TsExpressionParser;
|
|
ALeft, ARight: TsExprNode);
|
|
begin
|
|
FParser := AParser;
|
|
FLeft := ALeft;
|
|
FRight := ARight;
|
|
end;
|
|
|
|
destructor TsBinaryOperationExprNode.Destroy;
|
|
begin
|
|
FreeAndNil(FLeft);
|
|
FreeAndNil(FRight);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TsBinaryOperationExprNode.Has3DLink: Boolean;
|
|
begin
|
|
Result := FLeft.Has3DLink or FRight.Has3DLink;
|
|
end;
|
|
|
|
procedure TsBinaryOperationExprNode.IterateNodes(AProc: TsExprNodeProc;
|
|
AData1, AData2: Pointer; var MustRebuildFormulas: Boolean);
|
|
var
|
|
rebuildLeft: Boolean = false;
|
|
rebuildRight: Boolean = false;
|
|
begin
|
|
FLeft.IterateNodes(AProc, AData1, AData2, rebuildLeft);
|
|
FRight.IterateNodes(AProc, AData1, AData2, rebuildRight);
|
|
MustRebuildFormulas := MustRebuildFormulas or rebuildLeft or rebuildRight;
|
|
end;
|
|
|
|
function TsBinaryOperationExprNode.HasError(out AResult: TsExpressionResult): Boolean;
|
|
begin
|
|
Result := Left.HasError(AResult) or Right.HasError(AResult);
|
|
end;
|
|
|
|
|
|
{ TsBooleanOperationExprNode }
|
|
|
|
function TsBooleanOperationExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := Left.NodeType;
|
|
end;
|
|
|
|
|
|
{ TsConstExprNode }
|
|
|
|
constructor TsConstExprNode.CreateString(AParser: TsExpressionParser;
|
|
AValue: String);
|
|
begin
|
|
FParser := AParser;
|
|
FValue.ResultType := rtString;
|
|
FValue.ResString := AValue;
|
|
end;
|
|
|
|
constructor TsConstExprNode.CreateInteger(AParser: TsExpressionParser;
|
|
AValue: Int64);
|
|
begin
|
|
FParser := AParser;
|
|
FValue.ResultType := rtInteger;
|
|
FValue.ResInteger := AValue;
|
|
end;
|
|
|
|
constructor TsConstExprNode.CreateDateTime(AParser: TsExpressionParser;
|
|
AValue: TDateTime);
|
|
begin
|
|
FParser := AParser;
|
|
FValue.ResultType := rtDateTime;
|
|
FValue.ResDateTime := AValue;
|
|
end;
|
|
|
|
constructor TsConstExprNode.CreateFloat(AParser: TsExpressionParser;
|
|
AValue: TsExprFloat);
|
|
begin
|
|
FParser := AParser;
|
|
FValue.ResultType := rtFloat;
|
|
FValue.ResFloat := AValue;
|
|
end;
|
|
|
|
constructor TsConstExprNode.CreateBoolean(AParser: TsExpressionParser;
|
|
AValue: Boolean);
|
|
begin
|
|
FParser := AParser;
|
|
FValue.ResultType := rtBoolean;
|
|
FValue.ResBoolean := AValue;
|
|
end;
|
|
|
|
constructor TsConstExprNode.CreateError(AParser: TsExpressionParser;
|
|
AValue: TsErrorValue);
|
|
begin
|
|
FParser := AParser;
|
|
FValue.ResultType := rtError;
|
|
FValue.ResError := AValue;
|
|
end;
|
|
|
|
constructor TsConstExprNode.CreateError(AParser: TsExpressionParser;
|
|
AValue: String);
|
|
var
|
|
err: TsErrorValue;
|
|
begin
|
|
// Don't check for equal strings. If, for example, the column A of a cell
|
|
// reference A1 is deleted Excel replaces the A by '#REF!', i.e the
|
|
// reference becomes '#REF!1' (with the 1 at the end)!
|
|
if pos('#NULL!', AValue) > 0 then
|
|
// if AValue = '#NULL!' then
|
|
err := errEmptyIntersection
|
|
else if Pos('#DIV/0!', AValue) > 0 then
|
|
// else if AValue = '#DIV/0!' then
|
|
err := errDivideByZero
|
|
// else if AValue = '#VALUE!' then
|
|
else if Pos('#VALUE!', AValue) > 0 then
|
|
err := errWrongType
|
|
// else if AValue = '#REF!' then
|
|
else if Pos('#REF!', AValue) > 0 then
|
|
err := errIllegalRef
|
|
// else if AValue = '#NAME?' then
|
|
else if Pos('#NAME?', AValue) > 0 then
|
|
err := errWrongName
|
|
// else if AValue = '#NUM!' then
|
|
else if Pos('#NUM!', AValue) > 0 then
|
|
err := errOverflow
|
|
// else if AValue = '#N/A' then
|
|
else if Pos('#N/A', AValue) > 0 then
|
|
err := errArgError
|
|
// else if AValue = '#FORMULA?' then
|
|
else if Pos('#FORMULA?', AValue) > 0 then
|
|
err := errFormulaNotSupported
|
|
else
|
|
AParser.ParserError('Unknown error type.');
|
|
CreateError(AParser, err);
|
|
end;
|
|
|
|
function TsConstExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := FValue.ResultType;
|
|
end;
|
|
|
|
procedure TsConstExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
begin
|
|
AResult := 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, Parser.FFormatSettings^) + ''''; // Probably wrong !!!
|
|
rtBoolean : if FValue.ResBoolean then Result := 'TRUE' else Result := 'FALSE';
|
|
rtFloat : Result := FloatToStr(FValue.ResFloat, Parser.FFormatSettings^);
|
|
rtError : Result := GetErrorValueStr(FValue.ResError);
|
|
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);
|
|
rtError : Result := RPNErr(FValue.ResError, ANext);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TsMissingExprNode }
|
|
|
|
function TsMissingArgExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
|
|
begin
|
|
Result := RPNMissingARg(ANext);
|
|
end;
|
|
|
|
function TsMissingArgExprNode.AsString: String;
|
|
begin
|
|
Result := '';
|
|
end;
|
|
|
|
procedure TsMissingArgExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
begin
|
|
AResult.ResultType := rtMissingArg;
|
|
AResult.ResInteger := 0;
|
|
end;
|
|
|
|
function TsMissingArgExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := rtMissingArg;
|
|
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.GetNodeValue(out Result: TsExpressionResult);
|
|
var
|
|
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.GetNodeValue(out Result: TsExpressionResult);
|
|
var
|
|
cell: PCell;
|
|
val: Extended;
|
|
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 then
|
|
Result := FloatResult(0.0)
|
|
else if (cell^.ContentType = cctUTF8String) then begin
|
|
if TryStrToFloat(cell^.UTF8StringValue, val) then
|
|
Result := FloatResult(-val)
|
|
else
|
|
Result := ErrorResult(errWrongType);
|
|
end else
|
|
if (cell^.ContentType = cctNumber) or (cell^.ContentType = cctDateTime) then
|
|
begin
|
|
if frac(cell^.NumberValue) = 0.0 then
|
|
Result := IntegerResult(-trunc(cell^.NumberValue))
|
|
else
|
|
Result := FloatResult(cell^.NumberValue);
|
|
end else
|
|
if (cell^.ContentType = cctBool) then
|
|
Result := ErrorResult(errWrongType);
|
|
end;
|
|
rtEmpty:
|
|
Result := FloatResult(0.0);
|
|
rtString:
|
|
if TryStrToFloat(Result.ResString, val) then
|
|
Result := FloatResult(-val)
|
|
else
|
|
Result := ErrorResult(errWrongType);
|
|
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(rsNoPercentOperation, [ResultTypeName(Operand.NodeType), Operand.AsString])
|
|
end;
|
|
|
|
procedure TsPercentExprNode.GetNodeValue(out 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(out 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(out AResult: TsExpressionResult);
|
|
begin
|
|
Operand.GetNodeValue(AResult);
|
|
case AResult.ResultType of
|
|
rtBoolean : AResult.ResBoolean := not AResult.ResBoolean;
|
|
rtEmpty : AResult := BooleanResult(true); // This is according to Excel
|
|
end
|
|
end;
|
|
|
|
function TsNotExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := Operand.NodeType;
|
|
end;
|
|
*)
|
|
|
|
{ TsBooleanResultExprNode }
|
|
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
if Left.HasError(AResult) and Right.HasError(AResult) then
|
|
begin
|
|
AResult := BooleanResult(LRes.ResError = RRes.ResError);
|
|
exit;
|
|
end;
|
|
|
|
if HasError(AResult) then
|
|
exit;
|
|
|
|
if IsBlank(LRes) then
|
|
AResult := BooleanResult(IsBlank(RRes))
|
|
else if IsBlank(RRes) then
|
|
AResult := BooleanResult(IsBlank(LRes))
|
|
else if IsString(LRes) and IsString(RRes) then
|
|
AResult := BooleanResult(ArgToString(LRes) = ArgToString(RRes))
|
|
else begin
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := BooleanResult(false)
|
|
else
|
|
AResult := BooleanResult(fL = fR);
|
|
end;
|
|
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(out AResult: TsExpressionResult);
|
|
begin
|
|
inherited GetNodeValue(AResult);
|
|
if AResult.ResultType <> rtError then
|
|
AResult.ResBoolean := not AResult.ResBoolean;
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
if HasError(AResult) then
|
|
exit;
|
|
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
if IsBlank(LRes) or IsBlank(RRes) then
|
|
AResult := BooleanResult(false)
|
|
else
|
|
if IsString(LRes) and IsString(RRes) then
|
|
AResult := BooleanResult(ArgToString(LRes) < ArgToString(RRes))
|
|
else begin
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := BooleanResult(false)
|
|
else
|
|
AResult := BooleanResult(fL < fR);
|
|
end;
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
if HasError(AResult) then
|
|
exit;
|
|
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
if IsBlank(LRes) or IsBlank(RRes) then
|
|
AResult := BooleanResult(false)
|
|
else
|
|
if IsString(LRes) and IsString(RRes) then
|
|
AResult := BooleanResult(ArgToString(LRes) > ArgToString(RRes))
|
|
else begin
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := BooleanResult(false)
|
|
else
|
|
AResult := BooleanResult(fL > fR);
|
|
end;
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
if HasError(AResult) then
|
|
exit;
|
|
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
if IsBlank(LRes) then
|
|
AResult := BooleanResult(IsBlank(RRes))
|
|
else if IsBlank(RRes) then
|
|
AResult := BooleanResult(IsBlank(LRes))
|
|
else if IsString(LRes) and IsString(RRes) then
|
|
AResult := BooleanResult(ArgToString(LRes) >= ArgToString(RRes))
|
|
else begin
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := BooleanResult (false)
|
|
else
|
|
AResult := BooleanResult(fL >= fR);
|
|
end;
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
if HasError(AResult) then
|
|
exit;
|
|
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
if IsBlank(LRes) then
|
|
AResult := BooleanResult(IsBlank(RRes))
|
|
else if IsBlank(RRes) then
|
|
AResult := BooleanResult(IsBlank(LRes))
|
|
else if IsString(LRes) and IsString(RRes) then
|
|
AResult := BooleanResult(ArgToString(LRes) <= ArgToString(RRes))
|
|
else begin
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := BooleanResult (false)
|
|
else
|
|
AResult := BooleanResult(fL <= fR);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TsConcatExprNode }
|
|
|
|
function TsConcatExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
|
|
begin
|
|
Result := RPNFunc(fekConcat,
|
|
Right.AsRPNItem(
|
|
Left.AsRPNItem(
|
|
ANext)));
|
|
end;
|
|
|
|
function TsConcatExprNode.AsString: string;
|
|
begin
|
|
Result := Left.AsString + '&' + Right.AsString;
|
|
end;
|
|
|
|
procedure TsConcatExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes : TsExpressionResult;
|
|
begin
|
|
if HasError(AResult) then
|
|
exit;
|
|
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
AResult := StringResult(ArgToString(LRes) + ArgToString(RRes));
|
|
end;
|
|
|
|
function TsConcatExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := rtString;
|
|
end;
|
|
|
|
|
|
{ TsMathOperationExprNode }
|
|
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
{
|
|
if HasError(AResult) then
|
|
exit;
|
|
}
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := ErrorResult(errWrongType)
|
|
else
|
|
AResult := FloatResult(fL + fR);
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
lRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
{
|
|
if HasError(AResult) then
|
|
exit;
|
|
}
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := ErrorResult(errWrongType)
|
|
else
|
|
AResult := FloatResult(fL - fR);
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
{
|
|
if HasError(AResult) then
|
|
exit;
|
|
}
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := ErrorResult(errWrongType)
|
|
else
|
|
try
|
|
AResult := FloatResult(fL * fR);
|
|
except
|
|
on EInvalidArgument do AResult := ErrorResult(errOverflow);
|
|
end;
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
{
|
|
if HasError(AResult) then
|
|
exit;
|
|
}
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := ErrorResult(errWrongType)
|
|
else
|
|
if fR = 0.0 then
|
|
AResult := ErrorResult(errDivideByZero)
|
|
else
|
|
try
|
|
AResult := FloatResult(fL / fR);
|
|
except
|
|
on EInvalidArgument do AResult := ErrorResult(errOverflow);
|
|
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(out AResult: TsExpressionResult);
|
|
var
|
|
LRes, RRes: TsExpressionResult;
|
|
fL, fR: TsExprFloat;
|
|
begin
|
|
{
|
|
if HasError(AResult) then
|
|
exit;
|
|
}
|
|
Left.GetNodeValue(LRes);
|
|
Right.GetNodeValue(RRes);
|
|
fL := ArgToFloat(LRes);
|
|
fR := ArgToFloat(RRes);
|
|
if IsNaN(fL) or IsNaN(fR) then
|
|
AResult := ErrorResult(errWrongType)
|
|
else
|
|
try
|
|
AResult := FloatResult(Power(fL, fR));
|
|
except
|
|
on E: EInvalidArgument do AResult := ErrorResult(errOverflow);
|
|
end;
|
|
end;
|
|
|
|
function TsPowerExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := rtFloat;
|
|
end;
|
|
|
|
|
|
{ TsIdentifierExprNode }
|
|
|
|
constructor TsIdentifierExprNode.CreateIdentifier(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef);
|
|
begin
|
|
FParser := AParser;
|
|
FID := AID;
|
|
PResult := @FID.FValue;
|
|
FResultType := FID.ResultType;
|
|
end;
|
|
|
|
function TsIdentifierExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := FResultType;
|
|
end;
|
|
|
|
procedure TsIdentifierExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
begin
|
|
AResult := PResult^;
|
|
AResult.ResultType := FResultType;
|
|
end;
|
|
|
|
|
|
{ TsVariableExprNode }
|
|
|
|
function TsVariableExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
|
|
begin
|
|
Result := ANext; // Just a dummy assignment to silence the compiler...
|
|
RaiseParserError('Cannot handle variables for RPN, so far.');
|
|
end;
|
|
|
|
function TsVariableExprNode.AsString: string;
|
|
begin
|
|
Result := FID.Name;
|
|
end;
|
|
|
|
|
|
{ TsFunctionExprNode }
|
|
|
|
constructor TsFunctionExprNode.CreateFunction(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef; const Args: TsExprArgumentArray);
|
|
begin
|
|
inherited CreateIdentifier(AParser, 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:=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 + Parser.ListSeparator;
|
|
if Assigned(FArgumentNodes[i]) then
|
|
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
|
|
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(rsInvalidArgumentCount, [FID.Name]);
|
|
end;
|
|
|
|
for i := 0 to Length(FArgumentNodes)-1 do
|
|
begin
|
|
if FArgumentNodes[i] = nil then
|
|
Continue;
|
|
|
|
rta := FArgumentNodes[i].NodeType;
|
|
|
|
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;
|
|
// A "cell" can return any type --> no type conversion required here.
|
|
|
|
if rta = rtCell then
|
|
Continue;
|
|
end;
|
|
end;
|
|
|
|
function TsFunctionExprNode.Has3DLink: Boolean;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i := 0 to Length(FArgumentParams)-1 do
|
|
if FArgumentNodes[i].Has3DLink then exit(true);
|
|
Result := false;
|
|
end;
|
|
|
|
procedure TsFunctionExprNode.IterateNodes(AProc: TsExprNodeProc;
|
|
AData1, AData2: Pointer; var MustRebuildFormulas: Boolean);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to High(FArgumentParams) do
|
|
FArgumentNodes[i].IterateNodes(AProc, AData1, AData2, MustRebuildFormulas);
|
|
end;
|
|
|
|
|
|
{ TsFunctionCallBackExprNode }
|
|
|
|
constructor TsFunctionCallBackExprNode.CreateFunction(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef; const Args: TsExprArgumentArray);
|
|
begin
|
|
inherited;
|
|
FCallBack := AID.OnGetFunctionValueCallBack;
|
|
end;
|
|
|
|
procedure TsFunctionCallBackExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
begin
|
|
AResult.ResultType := NodeType; // was at end!
|
|
if Length(FArgumentParams) > 0 then
|
|
CalcParams;
|
|
FCallBack(AResult, FArgumentParams);
|
|
end;
|
|
|
|
|
|
{ TFPFunctionEventHandlerExprNode }
|
|
|
|
constructor TFPFunctionEventHandlerExprNode.CreateFunction(AParser: TsExpressionParser;
|
|
AID: TsExprIdentifierDef; const Args: TsExprArgumentArray);
|
|
begin
|
|
inherited;
|
|
FCallBack := AID.OnGetFunctionValue;
|
|
end;
|
|
|
|
procedure TFPFunctionEventHandlerExprNode.GetNodeValue(out Result: TsExpressionResult);
|
|
begin
|
|
Result.ResultType := NodeType; // was at end
|
|
if Length(FArgumentParams) > 0 then
|
|
CalcParams;
|
|
FCallBack(Result, FArgumentParams);
|
|
end;
|
|
|
|
|
|
{ TsCellExprNode }
|
|
|
|
{ AWorksheet -- sheet which contains the formula (needed for non-3d formulas)
|
|
ASheetIndex -- referenced sheet (needed for 3d formulas, empty for non-3d)
|
|
ARow, ACol -- row/col indexes of referenced cell
|
|
AFlags -- determines whether the reference is absolute or relative }
|
|
constructor TsCellExprNode.Create(AParser: TsExpressionParser;
|
|
AWorksheet: TsBasicWorksheet; ASheetName: String; ARow, ACol: Cardinal;
|
|
AFlags: TsRelFlags);
|
|
begin
|
|
FError := errOK;
|
|
FParser := AParser;
|
|
FWorksheet := AWorksheet;
|
|
if (ASheetName = '') then begin
|
|
FSheetIndex := -1;
|
|
FHas3DLink := false;
|
|
end else begin
|
|
FSheetIndex := TsWorkbook(GetWorkbook).GetWorksheetIndex(ASheetName);
|
|
if FSheetIndex = -1 then
|
|
FError := errIllegalRef;
|
|
FHas3DLink := true;
|
|
end;
|
|
FRow := ARow;
|
|
FCol := ACol;
|
|
FFlags := AFlags;
|
|
// FCell := TsWorksheet(FWorksheet).FindCell(FRow, FCol);
|
|
end;
|
|
|
|
function TsCellExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
|
|
begin
|
|
if FError <> errOK then
|
|
Result := RPNErr(FError, ANext)
|
|
else
|
|
if FIsRef then
|
|
begin
|
|
if Has3dLink then
|
|
Result := RPNCellRef3D(GetSheetIndex, GetRow, GetCol, FFlags, ANext)
|
|
else
|
|
Result := RPNCellRef(GetRow, GetCol, FFlags, ANext)
|
|
end else
|
|
begin
|
|
if Has3dLink then
|
|
Result := RPNCellValue3D(GetSheetIndex, GetRow, GetCol, FFlags, ANext)
|
|
else
|
|
Result := RPNCellValue(GetRow, GetCol, FFlags, ANext);
|
|
end;
|
|
end;
|
|
|
|
function TsCellExprNode.AsString: string;
|
|
var
|
|
r, c: Cardinal;
|
|
s: String;
|
|
begin
|
|
if FError <> errOK then begin
|
|
Result := GetErrorValueStr(FError);
|
|
exit;
|
|
end;
|
|
|
|
r := Getrow;
|
|
c := GetCol;
|
|
if Has3dLink then begin
|
|
case FParser.Dialect of
|
|
fdExcelA1, fdLocalized:
|
|
Result := Format('%s!%s', [GetQuotedSheetName, GetCellString(r, c, FFlags)]);
|
|
fdExcelR1C1:
|
|
if FParser.FSourceCell = nil then
|
|
Result := Format('%s!%s', [GetQuotedSheetName,
|
|
GetCellString_R1C1(r, c, [])])
|
|
else
|
|
Result := Format('%s!%s', [GetQuotedSheetName,
|
|
GetCellString_R1C1(r, c, FFlags, FParser.FSourceCell^.Row, FParser.FSourceCell^.Col)]);
|
|
fdOpenDocument:
|
|
begin
|
|
s := GetQuotedSheetName;
|
|
if s[1] = '''' then s := '$' + s;
|
|
Result := Format('[%s.%s]', [s, GetCellString(r, c, FFlags)]);
|
|
end;
|
|
end
|
|
end else
|
|
case FParser.Dialect of
|
|
fdExcelA1, fdLocalized:
|
|
Result := GetCellString(GetRow, GetCol, FFlags);
|
|
fdExcelR1C1:
|
|
if FParser.FSourceCell = nil then
|
|
Result := GetCellString_R1C1(GetRow, GetCol, [])
|
|
else
|
|
Result := GetCellString_R1C1(GetRow, GetCol, FFlags, FParser.FSourceCell^.Row, FParser.FSourceCell^.Col);
|
|
fdOpenDocument:
|
|
Result := '[.' + GetCellString(GetRow, GetCol, FFlags) + ']';
|
|
end;
|
|
end;
|
|
|
|
procedure TsCellExprNode.Check;
|
|
begin
|
|
// Nothing to check;
|
|
end;
|
|
|
|
{ Calculates the column address of the node's cell for various cases:
|
|
(1) Copy mode:
|
|
The "DestCell" of the parser is the cell for which the formula is
|
|
calculated. The "SourceCell" contains the formula. If the formula contains
|
|
a relative address in the cell node the function calculates the row
|
|
address of the cell represented by the node as seen from the DestCell.
|
|
If the formula contains an absolute address the function returns the row
|
|
address of the SourceCell.
|
|
(2) Normal mode:
|
|
Returns the "true" row address of the cell assigned to the formula node. }
|
|
function TsCellExprNode.GetCol: Cardinal;
|
|
begin
|
|
Result := FCol;
|
|
if FParser.CopyMode and (rfRelCol in FFlags) then
|
|
Result := FCol - FParser.FSourceCell^.Col + FParser.FDestCell^.Col;
|
|
end;
|
|
|
|
procedure TsCellExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
var
|
|
cell: PCell;
|
|
formula: PsFormula;
|
|
sheet: TsWorksheet;
|
|
begin
|
|
if FError <> errOK then begin
|
|
AResult.ResultType := rtError;
|
|
AResult.ResError := FError;
|
|
exit;
|
|
end;
|
|
|
|
cell := TsWorksheet(GetSheet).FindCell(GetRow, GetCol);
|
|
{
|
|
if Parser.CopyMode then
|
|
cell := (FWorksheet as TsWorksheet).FindCell(GetRow, GetCol)
|
|
else
|
|
cell := FCell;
|
|
}
|
|
|
|
if (cell <> nil) and HasFormula(cell) then begin
|
|
sheet := TsWorksheet(cell^.Worksheet);
|
|
formula := sheet.Formulas.FindFormula(cell^.Row, cell^.Col);
|
|
case formula^.CalcState of
|
|
csNotCalculated:
|
|
sheet.CalcFormula(formula);
|
|
csCalculating:
|
|
raise ECalcEngine.CreateFmt(rsCircularReference, [GetCellString(cell^.Row, cell^.Col)]);
|
|
end;
|
|
end;
|
|
|
|
AResult.ResultType := rtCell;
|
|
AResult.ResRow := GetRow;
|
|
AResult.ResCol := GetCol;
|
|
AResult.Worksheet := GetSheet;
|
|
end;
|
|
|
|
function TsCellExprNode.GetQuotedSheetName: String;
|
|
begin
|
|
Result := GetSheetName;
|
|
if SheetNameNeedsQuotes(Result) then
|
|
Result := QuotedStr(Result);
|
|
end;
|
|
|
|
{ See: GetCol }
|
|
function TsCellExprNode.GetRow: Cardinal;
|
|
begin
|
|
Result := FRow;
|
|
if Parser.CopyMode and (rfRelRow in FFlags) then
|
|
Result := FRow - FParser.FSourceCell^.Row + FParser.FDestCell^.Row;
|
|
end;
|
|
|
|
function TsCellExprNode.GetSheet: TsBasicWorksheet;
|
|
begin
|
|
if FHas3dLink then begin
|
|
Result := (GetWorkbook as TsWorkbook).GetWorksheetByIndex(FSheetIndex);
|
|
if Result = nil then FError := errIllegalREF;
|
|
end else
|
|
Result := FWorksheet;
|
|
end;
|
|
|
|
function TsCellExprNode.GetSheetIndex: Integer;
|
|
var
|
|
book: TsWorkbook;
|
|
begin
|
|
if FHas3dLink then
|
|
Result := FSheetIndex
|
|
else begin
|
|
book := GetWorkbook as TsWorkbook;
|
|
Result := book.GetWorksheetIndex(FWorksheet)
|
|
end;
|
|
end;
|
|
|
|
function TsCellExprNode.GetSheetName: String;
|
|
begin
|
|
if FHas3dLink then
|
|
Result := TsWorkbook(GetWorkbook).GetWorksheetByIndex(FSheetIndex).Name
|
|
else
|
|
Result := FWorksheet.Name;
|
|
end;
|
|
|
|
function TsCellExprNode.GetWorkbook: TsBasicWorkbook;
|
|
begin
|
|
Result := (FWorksheet as TsWorksheet).Workbook;
|
|
end;
|
|
|
|
function TsCellExprNode.Has3DLink: Boolean;
|
|
begin
|
|
Result := FHas3dLink;
|
|
end;
|
|
|
|
function TsCellExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := rtCell;
|
|
end;
|
|
|
|
procedure TsCellExprNode.IterateNodes(AProc: TsExprNodeProc;
|
|
AData1, AData2: Pointer; var MustRebuildFormulas: Boolean);
|
|
begin
|
|
AProc(self, AData1, AData2, MustRebuildFormulas);
|
|
end;
|
|
|
|
procedure TsCellExprNode.SetSheetIndex(AIndex: Integer);
|
|
begin
|
|
FSheetIndex := AIndex;
|
|
end;
|
|
|
|
|
|
{ TsCellRangeExprNode }
|
|
|
|
constructor TsCellRangeExprNode.Create(AParser: TsExpressionParser;
|
|
AWorksheet: TsBasicWorksheet; ASheet1, ASheet2: String; ARange: TsCellRange;
|
|
AFlags: TsRelFlags);
|
|
var
|
|
book: TsWorkbook;
|
|
begin
|
|
if (ASheet1 = '') and (ASheet2 <> '') then
|
|
raise Exception.Create('Invalid parameters in cell range');
|
|
|
|
FParser := AParser;
|
|
FWorksheet := AWorksheet;
|
|
FFlags := [];
|
|
FError := errOK;
|
|
book := TsWorkbook(GetWorkbook);
|
|
|
|
F3dRange := ((ASheet1 <> '') and (ASheet2 <> '') { and (ASheet1 <> ASheet2)}) or
|
|
((ASheet1 <> '') and (ASheet2 = ''));
|
|
|
|
FSheetIndex[1] := book.GetWorksheetIndex(ASheet1);
|
|
if (FSheetIndex[1] = -1) and (ASheet1 <> '') then
|
|
FError := errIllegalREF
|
|
else
|
|
if ASheet2 <> '' then begin
|
|
FSheetIndex[2] := book.GetWorksheetIndex(ASheet2);
|
|
if (FSheetIndex[2] = -1) and (ASheet2 <> '') then
|
|
FError := errIllegalREF;
|
|
end else
|
|
FSheetIndex[2] := FSheetIndex[1];
|
|
EnsureOrder(FSheetIndex[1], FSheetIndex[2]);
|
|
|
|
if ARange.Row2 = Cardinal(-1) then
|
|
ARange.Row2 := ARange.Row1;
|
|
if ARange.Row1 <= ARange.Row2 then
|
|
begin
|
|
FRow[1] := ARange.Row1;
|
|
FRow[2] := ARange.Row2;
|
|
FCol[1] := ARange.Col1;
|
|
if rfRelRow in AFlags then Include(FFlags, rfRelRow);
|
|
if rfRelRow2 in AFlags then Include(FFlags, rfRelRow2);
|
|
end else
|
|
begin
|
|
FRow[1] := ARange.Row2;
|
|
FRow[2] := ARange.Row1;
|
|
if rfRelRow in AFlags then Include(FFlags, rfRelRow2);
|
|
if rfRelRow2 in AFlags then Include(FFlags, rfRelRow);
|
|
end;
|
|
|
|
if ARange.Col2 = Cardinal(-1) then
|
|
ARange.Col2 := ARange.Col1;
|
|
if ARange.Col1 <= ARange.Col2 then
|
|
begin
|
|
FCol[1] := ARange.Col1;
|
|
FCol[2] := ARange.Col2;
|
|
if (rfRelCol in AFlags) then Include(FFlags, rfRelCol);
|
|
if (rfRelCol2 in AFlags) then Include(FFlags, rfRelCol2);
|
|
end else
|
|
begin
|
|
FCol[1] := ARange.Col2;
|
|
FCol[2] := ARange.Col1;
|
|
if (rfRelCol in AFlags) then Include(FFlags, rfRelCol2);
|
|
if (rfRelCol2 in AFlags) then Include(FFlags, rfRelCol);
|
|
end;
|
|
|
|
if Has3DLink then FParser.FContains3DRef := true;
|
|
end;
|
|
|
|
function TsCellRangeExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
|
|
begin
|
|
if FError <> errOK then
|
|
Result := RPNErr(FError, ANext)
|
|
else
|
|
if F3dRange then
|
|
Result := RPNCellRange3D(
|
|
FSheetIndex[1], GetRow(1), Integer(GetCol(1)),
|
|
FSheetIndex[2], GetRow(2), Integer(GetCol(2)),
|
|
FFlags, ANext
|
|
)
|
|
else
|
|
Result := RPNCellRange(
|
|
GetRow(1), GetCol(1),
|
|
GetRow(2), GetCol(2),
|
|
FFlags, ANext
|
|
);
|
|
end;
|
|
|
|
function TsCellRangeExprNode.AsString: string;
|
|
var
|
|
r1, c1, r2, c2: Cardinal;
|
|
s1, s2: String;
|
|
begin
|
|
if FError <> errOK then begin
|
|
Result := GetErrorValueStr(FError);
|
|
exit;
|
|
end;
|
|
|
|
if FSheetIndex[1] = -1 then
|
|
s1 := FWorksheet.Name
|
|
else
|
|
s1 := (Workbook as TsWorkbook).GetWorksheetByIndex(FSheetIndex[1]).Name;
|
|
if SheetNameNeedsQuotes(s1) then s1 := QuotedStr(s1);
|
|
|
|
if FSheetIndex[2] = -1 then
|
|
s2 := FWorksheet.Name
|
|
else
|
|
s2 := (Workbook as TsWorkbook).GetWorksheetByIndex(FSheetIndex[2]).Name;
|
|
if SheetNameNeedsQuotes(s2) then s2 := QuotedStr(s2);
|
|
|
|
r1 := GetRow(1);
|
|
c1 := GetCol(1);
|
|
r2 := GetRow(2);
|
|
c2 := GetCol(2);
|
|
|
|
if F3dRange then
|
|
case FParser.Dialect of
|
|
fdExcelA1, fdLocalized:
|
|
Result := GetCellRangeString(s1, s2, r1, c1, r2, c2, FFlags, true);
|
|
fdExcelR1C1:
|
|
Result := GetCellRangeString_R1C1(s1, s2, r1, c1, r2, c2, FFlags,
|
|
FParser.FSourceCell^.Row, FParser.FSourceCell^.Col);
|
|
fdOpenDocument:
|
|
begin
|
|
if (s1[1] = '''') then s1 := '$' + s1;
|
|
if (s2[1] = '''') then s2 := '$' + s2;
|
|
Result := GetCellRangeString_ODS(s1, s2, r1, c1, r2, c2, FFlags);
|
|
end;
|
|
end
|
|
else
|
|
case FParser.Dialect of
|
|
fdExcelA1, fdLocalized:
|
|
Result := GetCellRangeString(r1, c1, r2, c2, FFlags, true);
|
|
fdExcelR1C1:
|
|
Result := GetCellRangeString_R1C1(r1, c1, r2, c2, FFlags,
|
|
FParser.FSourceCell^.Row, FParser.FSourceCell^.Col);
|
|
fdOpenDocument:
|
|
Result := GetCellRangeString_ODS(r1, c1, r2, c2, FFlags, true);
|
|
end;
|
|
end;
|
|
|
|
procedure TsCellRangeExprNode.Check;
|
|
begin
|
|
// Nothing to check;
|
|
end;
|
|
|
|
{ Calculates the column address of the node's cell for various cases:
|
|
(1) Copy mode:
|
|
The "DestCell" of the parser is the cell for which the formula is
|
|
calculated. The "SourceCell" contains the formula. If the formula contains
|
|
a relative address in the cell node the function calculates the row
|
|
address of the cell represented by the node as seen from the DestCell.
|
|
If the formula contains an absolute address the function returns the row
|
|
address of the SourceCell.
|
|
(2) Normal mode:
|
|
Returns the "true" row address of the cell assigned to the formula node. }
|
|
function TsCellRangeExprNode.GetCol(AIndex: TsCellRangeIndex): Cardinal;
|
|
begin
|
|
Result := FCol[AIndex];
|
|
if FParser.CopyMode and (rfRelCol in FFlags) then
|
|
Result := FCol[AIndex] - FParser.FSourceCell^.Col + FParser.FDestCell^.Col;
|
|
end;
|
|
|
|
procedure TsCellRangeExprNode.GetNodeValue(out AResult: TsExpressionResult);
|
|
var
|
|
r, c, s: Array[TsCellRangeIndex] of Integer; //Cardinal;
|
|
ss: Integer;
|
|
i: TsCellRangeIndex;
|
|
sheet: TsWorksheet;
|
|
formula: PsFormula;
|
|
begin
|
|
if FError <> errOK then begin
|
|
AResult.ResultType := rtError;
|
|
AResult.ResError := FError;
|
|
exit;
|
|
end;
|
|
|
|
for i in TsCellRangeIndex do
|
|
begin
|
|
r[i] := GetRow(i);
|
|
c[i] := GetCol(i);
|
|
s[i] := FSheetIndex[i];
|
|
end;
|
|
|
|
if not F3dRange then begin
|
|
s[1] := (Workbook as TsWorkbook).GetWorksheetIndex(FWorksheet);
|
|
s[2] := s[1];
|
|
end;
|
|
|
|
for ss := s[1] to s[2] do begin
|
|
sheet := (Workbook as TsWorkbook).GetWorksheetByIndex(ss);
|
|
if sheet = nil then begin
|
|
AResult := ErrorResult(errIllegalRef);
|
|
exit;
|
|
end;
|
|
for formula in sheet.Formulas do
|
|
if (Integer(formula^.Row) >= r[1]) and (Integer(formula^.Row) <= r[2]) and
|
|
(Integer(formula^.Col) >= c[1]) and (Integer(formula^.Col) <= c[2])
|
|
then
|
|
case formula^.CalcState of
|
|
csNotCalculated:
|
|
sheet.CalcFormula(formula);
|
|
csCalculating:
|
|
raise ECalcEngine.Create(rsCircularReference);
|
|
end;
|
|
end;
|
|
|
|
AResult.ResultType := rtCellRange;
|
|
AResult.ResCellRange.Row1 := r[1];
|
|
AResult.ResCellRange.Col1 := c[1];
|
|
AResult.ResCellRange.Row2 := r[2];
|
|
AResult.ResCellRange.Col2 := c[2];
|
|
AResult.ResCellRange.Sheet1 := s[1];
|
|
AResult.ResCellRange.Sheet2 := s[2];
|
|
AResult.Worksheet := FWorksheet;
|
|
end;
|
|
|
|
// Be careful when modifying GetRange - it may break everything
|
|
function TsCellRangeExprNode.GetRange: TsCellRange;
|
|
begin
|
|
Result.Row1 := FRow[1];
|
|
Result.Col1 := FCol[1];
|
|
Result.Row2 := FRow[2];
|
|
Result.Col2 := FCol[2];
|
|
end;
|
|
|
|
function TsCellRangeExprNode.GetRow(AIndex: TsCellRangeIndex): Cardinal;
|
|
begin
|
|
Result := FRow[AIndex];
|
|
if FParser.CopyMode and (rfRelRow in FFlags) then
|
|
Result := FRow[AIndex] - FParser.FSourceCell^.Row + FParser.FDestCell^.Row;
|
|
end;
|
|
|
|
function TsCellRangeExprNode.GetWorkbook: TsBasicWorkbook;
|
|
begin
|
|
if FWorksheet = nil then
|
|
Result := nil
|
|
else
|
|
Result := (FWorksheet as TsWorksheet).Workbook;
|
|
end;
|
|
|
|
function TsCellRangeExprNode.GetSheet(AIndex: TsCellRangeIndex): TsBasicWorksheet;
|
|
begin
|
|
if FError <> errOK then
|
|
Result := nil
|
|
else
|
|
Result := TsWorkbook(GetWorkbook).GetWorksheetByIndex(GetSheetIndex(AIndex));
|
|
end;
|
|
|
|
function TsCellRangeExprNode.GetSheetIndex(AIndex: TsCellRangeIndex): Integer;
|
|
begin
|
|
Result := FSheetIndex[AIndex];
|
|
end;
|
|
|
|
function TsCellRangeExprNode.Has3DLink: Boolean;
|
|
begin
|
|
Result := F3dRange;
|
|
end;
|
|
|
|
procedure TsCellRangeExprNode.IterateNodes(AProc: TsExprNodeProc;
|
|
AData1, AData2: Pointer; var MustRebuildFormulas: Boolean);
|
|
begin
|
|
AProc(self, AData1, AData2, MustRebuildFormulas);
|
|
end;
|
|
|
|
function TsCellRangeExprNode.NodeType: TsResultType;
|
|
begin
|
|
Result := rtCellRange;
|
|
end;
|
|
|
|
procedure TsCellRangeExprNode.SetRange(const ARange: TsCellRange);
|
|
begin
|
|
FRow[1] := ARange.Row1;
|
|
FCol[1] := ARange.Col1;
|
|
FRow[2] := ARange.Row2;
|
|
FCol[2] := ARange.Col2;
|
|
end;
|
|
|
|
procedure TsCellRangeExprNode.SetSheetIndex(AIndex: TsCellRangeIndex;
|
|
AValue: Integer);
|
|
begin
|
|
FSheetIndex[AIndex] := AValue;
|
|
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 as TsWorksheet).FindCell(Arg.ResRow, Arg.ResCol)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function ArgToInt(Arg: TsExpressionResult): Integer;
|
|
var
|
|
cell: PCell;
|
|
begin
|
|
Result := 0;
|
|
case Arg.ResultType of
|
|
rtInteger : result := Arg.ResInteger;
|
|
rtFloat : result := trunc(Arg.ResFloat);
|
|
rtDateTime : result := trunc(Arg.ResDateTime);
|
|
rtBoolean : if Arg.ResBoolean then Result := 1 else Result := 0;
|
|
rtString,
|
|
rtHyperlink : TryStrToInt(ArgToString(Arg), Result);
|
|
rtCell : begin
|
|
cell := ArgToCell(Arg);
|
|
if Assigned(cell) then
|
|
case cell^.ContentType of
|
|
cctNumber : result := trunc(cell^.NumberValue);
|
|
cctDateTime : result := trunc(cell^.DateTimeValue);
|
|
cctBool : if cell^.BoolValue then result := 1;
|
|
cctUTF8String: if not TryStrToInt(cell^.UTF8StringValue, result)
|
|
then Result := 0;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Utility function for the built-in math functions. Accepts also integers and
|
|
other data types in place of floating point arguments. To be called in
|
|
builtins or user-defined callbacks having float results or arguments. }
|
|
function ArgToFloat(Arg: TsExpressionResult): TsExprFloat;
|
|
var
|
|
cell: PCell;
|
|
s: String;
|
|
fs: TFormatSettings;
|
|
begin
|
|
Result := 0.0;
|
|
case Arg.ResultType of
|
|
rtInteger : result := Arg.ResInteger;
|
|
rtDateTime : result := Arg.ResDateTime;
|
|
rtFloat : result := Arg.ResFloat;
|
|
rtBoolean : if Arg.ResBoolean then Result := 1.0;
|
|
rtString,
|
|
rtHyperlink : TryStrToFloat(ArgToString(Arg), Result);
|
|
rtError : Result := NaN;
|
|
rtCell : begin
|
|
cell := ArgToCell(Arg);
|
|
if Assigned(cell) then
|
|
case cell^.ContentType of
|
|
cctNumber:
|
|
Result := cell^.NumberValue;
|
|
cctDateTime:
|
|
Result := cell^.DateTimeValue;
|
|
cctBool:
|
|
if cell^.BoolValue then result := 1.0;
|
|
cctUTF8String:
|
|
begin
|
|
fs := (Arg.Worksheet as TsWorksheet).Workbook.FormatSettings;
|
|
s := cell^.UTF8StringValue;
|
|
if not TryStrToFloat(s, Result, fs) then
|
|
Result := NaN;
|
|
end;
|
|
cctError:
|
|
Result := NaN;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function ArgToDateTime(Arg: TsExpressionResult): TDateTime;
|
|
var
|
|
cell: PCell;
|
|
fs: TFormatSettings;
|
|
begin
|
|
Result := 0.0;
|
|
case Arg.ResultType of
|
|
rtDateTime : result := Arg.ResDateTime;
|
|
rtInteger : Result := Arg.ResInteger;
|
|
rtFloat : Result := Arg.ResFloat;
|
|
rtBoolean : if Arg.ResBoolean then Result := 1.0;
|
|
rtHyperlink,
|
|
rtString : begin
|
|
fs := ExprFormatSettings;
|
|
if not TryStrToDateTime(ArgToString(Arg), Result, fs) then
|
|
Result := NaN;
|
|
end;
|
|
rtCell : begin
|
|
cell := ArgToCell(Arg);
|
|
if Assigned(cell) and (cell^.ContentType = cctDateTime) then
|
|
Result := cell^.DateTimeValue;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function ArgToString(Arg: TsExpressionResult): String;
|
|
// The Office applications are very fuzzy about data types...
|
|
var
|
|
cell: PCell;
|
|
fs: TFormatSettings;
|
|
dt: TDateTime;
|
|
p: Integer;
|
|
s: String;
|
|
begin
|
|
Result := '';
|
|
case Arg.ResultType of
|
|
rtString : result := Arg.ResString;
|
|
rtHyperlink : begin
|
|
s := Arg.ResString;
|
|
p := pos(HYPERLINK_SEPARATOR, s);
|
|
if p = 0 then
|
|
Result := s
|
|
else
|
|
Result := Copy(s, p + Length(HYPERLINK_SEPARATOR), Length(s));
|
|
end;
|
|
rtInteger : Result := IntToStr(Arg.ResInteger);
|
|
rtFloat : Result := FloatToStr(Arg.ResFloat);
|
|
rtBoolean : if Arg.ResBoolean then Result := '1' else Result := '0';
|
|
rtCell : begin
|
|
cell := ArgToCell(Arg);
|
|
if Assigned(cell) then
|
|
case cell^.ContentType of
|
|
cctUTF8String : Result := cell^.UTF8Stringvalue;
|
|
cctNumber : Result := Format('%g', [cell^.NumberValue]);
|
|
cctBool : if cell^.BoolValue then Result := '1' else Result := '0';
|
|
cctDateTime : begin
|
|
fs := (Arg.Worksheet as TsWorksheet).Workbook.FormatSettings;
|
|
dt := cell^.DateTimeValue;
|
|
if frac(dt) = 0.0 then
|
|
Result := FormatDateTime(fs.LongTimeFormat, dt, fs)
|
|
else
|
|
if trunc(dt) = 0 then
|
|
Result := FormatDateTime(fs.ShortDateFormat, dt, fs)
|
|
else
|
|
Result := FormatDateTime('cc', dt, fs);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure ArgsToFloatArray(const Args: TsExprParameterArray;
|
|
out AData: TsExprFloatArray; out AError: TsErrorValue);
|
|
const
|
|
BLOCKSIZE = 128;
|
|
var
|
|
i, n: Integer;
|
|
r, c: Cardinal;
|
|
cell: PCell;
|
|
sheet: TsWorksheet;
|
|
arg: TsExpressionResult;
|
|
idx, idx1, idx2: Integer;
|
|
begin
|
|
AError := errOK;
|
|
SetLength(AData{%H-}, BLOCKSIZE);
|
|
n := 0;
|
|
for i:=Low(Args) to High(Args) do
|
|
begin
|
|
arg := Args[i];
|
|
if arg.ResultType = rtError then begin
|
|
AError := arg.ResError;
|
|
exit;
|
|
end;
|
|
if arg.ResultType = rtCellRange then begin
|
|
idx1 := arg.ResCellRange.Sheet1;
|
|
idx2 := arg.ResCellRange.Sheet2;
|
|
for idx := idx1 to idx2 do
|
|
begin
|
|
sheet := (arg.Worksheet as TsWorksheet).Workbook.GetWorksheetByIndex(idx);
|
|
for r := arg.ResCellRange.Row1 to arg.ResCellRange.Row2 do
|
|
for c := arg.ResCellRange.Col1 to arg.ResCellRange.Col2 do
|
|
begin
|
|
cell := sheet.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
|
|
end;
|
|
end else
|
|
if (arg.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtBoolean]) then
|
|
begin
|
|
AData[n] := ArgToFloat(arg);
|
|
inc(n);
|
|
if n = Length(AData) then SetLength(AData, Length(AData) + BLOCKSIZE);
|
|
end;
|
|
end;
|
|
SetLength(AData, n);
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------}
|
|
{ Conversion of simple data types to ExpressionResults }
|
|
{------------------------------------------------------------------------------}
|
|
|
|
function BooleanResult(AValue: Boolean): TsExpressionResult;
|
|
begin
|
|
Result.ResultType := rtBoolean;
|
|
Result.ResBoolean := AValue;
|
|
end;
|
|
|
|
function CellResult(AValue: String): TsExpressionResult;
|
|
begin
|
|
Result.ResultType := rtCell;
|
|
ParseCellString(AValue, Result.ResRow, Result.ResCol);
|
|
end;
|
|
|
|
function CellResult(ACellRow, ACellCol: Cardinal): TsExpressionResult;
|
|
begin
|
|
Result.ResultType := rtCell;
|
|
Result.ResRow := ACellRow;
|
|
Result.ResCol := ACellCol;
|
|
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
|
|
if IsNaN(AValue) then
|
|
Result := ErrorResult(errWrongType)
|
|
else begin
|
|
Result.ResultType := rtFloat;
|
|
Result.ResFloat := AValue;
|
|
end;
|
|
end;
|
|
|
|
function IntegerResult(const AValue: Integer): TsExpressionResult;
|
|
begin
|
|
Result.ResultType := rtInteger;
|
|
Result.ResInteger := AValue;
|
|
end;
|
|
|
|
function IsBlank(const AValue: TsExpressionResult): Boolean;
|
|
var
|
|
cell: PCell;
|
|
begin
|
|
case AValue.ResultType of
|
|
rtString :
|
|
Result := (AValue.ResString = '');
|
|
rtInteger, rtFloat, rtBoolean, rtError:
|
|
Result := false;
|
|
rtEmpty:
|
|
Result := true;
|
|
rtCell:
|
|
begin
|
|
cell := (AValue.Worksheet as TsWorksheet).FindCell(AValue.ResRow, AValue.ResCol);
|
|
Result := (cell = nil) or (cell^.ContentType = cctEmpty) or
|
|
((cell^.ContentType = cctUTF8String) and (cell^.UTF8StringValue = ''));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsInteger(const AValue: TsExpressionResult): Boolean;
|
|
var
|
|
i: Int64;
|
|
cell: PCell;
|
|
begin
|
|
Result := false;
|
|
case AValue.ResultType of
|
|
rtString : Result := TryStrToInt64(AValue.ResString, i);
|
|
rtInteger: Result := true;
|
|
rtFloat : Result := (frac(AValue.ResFloat) = 0);
|
|
rtEmpty : Result := true;
|
|
rtCell : begin
|
|
cell := (AValue.Worksheet as TsWorksheet).FindCell(AValue.ResRow, AValue.ResCol);
|
|
if Assigned(cell) then
|
|
case cell^.ContentType of
|
|
cctNumber:
|
|
Result := frac(cell^.NumberValue) = 0.0;
|
|
cctDateTime:
|
|
Result := frac(cell^.DateTimeValue) = 0.0;
|
|
cctUTF8String:
|
|
Result := TryStrToInt64(cell^.UTF8StringValue, i);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsString(const AValue: TsExpressionResult): Boolean;
|
|
var
|
|
cell: PCell;
|
|
begin
|
|
Result := false;
|
|
case AValue.ResultType of
|
|
rtString: Result := true;
|
|
rtCell : begin
|
|
cell := (AValue.Worksheet as TsWorksheet).FindCell(AValue.ResRow, AValue.ResCol);
|
|
Result := (cell <> nil) and (cell^.ContentType = cctUTF8String);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function StringResult(const AValue: string): TsExpressionResult;
|
|
begin
|
|
Result.ResultType := rtString;
|
|
Result.ResString := AValue;
|
|
end;
|
|
|
|
|
|
function ConvertFormulaDialect(AFormula: String;
|
|
ASrcDialect, ADestDialect: TsFormulaDialect; AWorksheet: TsBasicWorksheet): String;
|
|
var
|
|
parser: TsSpreadsheetParser;
|
|
begin
|
|
if ASrcDialect = ADestDialect then
|
|
begin
|
|
Result := AFormula;
|
|
exit;
|
|
end;
|
|
|
|
if (ASrcDialect = fdExcelR1C1) or (ADestDialect = fdExcelR1C1) then
|
|
raise Exception.Create('ConvertFormulaDialect cannot be used for Excel R1C1 syntax.');
|
|
|
|
parser := TsSpreadsheetParser.Create(AWorksheet);
|
|
try
|
|
try
|
|
parser.Expression[ASrcDialect] := AFormula; // Parse in source dialect
|
|
Result := parser.Expression[ADestDialect]; // Convert to destination dialect
|
|
except
|
|
on EGeneralExprParserError do
|
|
begin
|
|
Result := AFormula;
|
|
(AWorksheet as TsWorksheet).Workbook.AddErrorMsg('Error converting formula "' + AFormula + '"');
|
|
end;
|
|
end;
|
|
finally
|
|
parser.Free;
|
|
end;
|
|
|
|
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;
|
|
|
|
procedure RegisterFunction(const AName: ShortString; const AResultType: Char;
|
|
const AParamTypes: String; const AExcelCode: Integer; ACallback: TsExprFunctionEvent);
|
|
begin
|
|
with BuiltinIdentifiers do
|
|
AddFunction(bcUser, AName, AResultType, AParamTypes, AExcelCode, ACallBack);
|
|
end;
|
|
|
|
|
|
{ TsBuiltInExprIdentifierDef }
|
|
|
|
procedure TsBuiltInExprIdentifierDef.Assign(Source: TPersistent);
|
|
begin
|
|
inherited Assign(Source);
|
|
if Source is TsBuiltInExprIdentifierDef then
|
|
FCategory := (Source as TsBuiltInExprIdentifierDef).Category;
|
|
end;
|
|
|
|
initialization
|
|
// These are the format settings used in storage of parsed formulas.
|
|
ExprFormatSettings := InitFormatSettings(nil);
|
|
{
|
|
ExprFormatSettings.ShortDateFormat := 'yyyy/m/d'; // the parser returns single digits
|
|
ExprFormatSettings.LongTimeFormat := 'h:n:s';
|
|
ExprFormatSettings.ShortTimeFormat := 'h:n';
|
|
}
|
|
|
|
RegisterStdBuiltins(BuiltinIdentifiers);
|
|
|
|
finalization
|
|
FreeBuiltins;
|
|
|
|
end.
|