fpspreadsheet: Refactor dialects in formula parser.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7067 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2019-07-22 17:49:17 +00:00
parent bc94843c24
commit 47e9e05399
8 changed files with 178 additions and 134 deletions

View File

@@ -673,6 +673,7 @@ type
end;
EExprScanner = class(Exception);
PFormatSettings = ^TFormatSettings;
{ TsExpressionParser }
TsExpressionParser = class
@@ -695,25 +696,27 @@ type
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: TFormatSettings;
FFormatSettings: PFormatSettings;
FContains3DRef: Boolean;
class function BuiltinExpressionManager: TsBuiltInExpressionManager;
function BuildStringFormula: String;
procedure ParserError(Msg: String);
function GetExpression: String;
function GetLocalizedExpression(const AFormatSettings: TFormatSettings): String; virtual;
function GetR1C1Expression(ACell: PCell): String;
procedure SetExpression(const AValue: String);
procedure SetLocalizedExpression(const AFormatSettings: TFormatSettings;
const AValue: String); virtual;
procedure SetR1C1Expression(ACell: PCell; const AValue: 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;
@@ -753,14 +756,22 @@ type
property AsString: String read GetAsString;
property AsBoolean: Boolean read GetAsBoolean;
property AsDateTime: TDateTime read GetAsDateTime;
// The expression to parse
property Expression: String read GetExpression write SetExpression;
property ListSeparator: Char read FListSep;
property LocalizedExpression[AFormatSettings: TFormatSettings]: String
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 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;
@@ -1197,7 +1208,7 @@ var
begin
C := CurrentChar;
prevC := #0;
while (not IsWordDelim(C) or (prevC = 'E') or (C = FParser.FFormatSettings.DecimalSeparator)) and (C <> cNull) do
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'))
@@ -1209,7 +1220,7 @@ begin
prevC := Upcase(C);
C := NextPos;
end;
if not TryStrToFloat(FToken, X, FParser.FFormatSettings) then
if not TryStrToFloat(FToken, X, FParser.FFormatSettings^) then
ScanError(Format(rsInvalidNumber, [FToken]));
Result := ttNumber;
end;
@@ -1382,7 +1393,7 @@ end;
function TsExpressionScanner.IsDigit(C: Char): Boolean;
begin
Result := (C in Digits) or (C = FParser.FFormatSettings.DecimalSeparator);
Result := (C in Digits) or (C = FParser.DecimalSeparator);
end;
function TsExpressionScanner.IsWordDelim(C: Char): Boolean;
@@ -1440,8 +1451,12 @@ begin
FIdentifiers.FParser := Self;
FScanner := TsExpressionScanner.Create(self);
FHashList := TFPHashObjectList.Create(False);
SetDialect(fdExcelA1);
FFormatSettings := InitFormatSettings(TsWorksheet(FWorksheet).Workbook);
// 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;
@@ -1616,23 +1631,6 @@ begin
Result := Res.ResString;
end;
{ Returns the expression in R1C1 notation.
ACell is the cell to which the expression is assumed to be relative. }
function TsExpressionParser.GetR1C1Expression(ACell: PCell): String;
var
oldDialect: TsFormulaDialect;
begin
oldDialect := FDialect;
try
FDialect := fdExcelR1C1;
PrepareCopyMode(ACell, ACell);
Result := Expression;
finally
PrepareCopyMode(nil, nil);
FDialect := oldDialect;
end;
end;
function TsExpressionParser.GetRPNFormula: TsRPNFormula;
begin
Result := CreateRPNFormula(FExprNode.AsRPNItem(nil), true);
@@ -1877,7 +1875,7 @@ begin
if TryStrToInt64(CurrentToken, I) then
Result := TsConstExprNode.CreateInteger(self, I)
else
if TryStrToFloat(CurrentToken, X, FFormatSettings) then
if TryStrToFloat(CurrentToken, X, FFormatSettings^) then
Result := TsConstExprNode.CreateFloat(self, X)
else
ParserError(Format(rsInvalidFloat, [CurrentToken]));
@@ -2007,19 +2005,61 @@ begin
FDirty := true;
end;
function TsExpressionParser.GetExpression: String;
function TsExpressionParser.GetDecimalSeparator: Char;
begin
Result := BuildStringFormula; //(FFormatSettings);
Result := FFormatSettings^.DecimalSeparator;
end;
function TsExpressionParser.GetLocalizedExpression(const AFormatSettings: TFormatSettings): String;
{ 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
// ExprFormatSettings := AFormatSettings;
FFormatSettings := AFormatSettings;
UpdateExprFormatSettings;
Result := BuildStringFormula; //(AFormatSettings);
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;
@@ -2034,42 +2074,73 @@ end;
procedure TsExpressionParser.SetDialect(const AValue: TsFormulaDialect);
begin
if FDialect = AValue then
exit;
FDialect := AValue;
case FDialect of
fdExcelA1,
fdExcelR1C1 : FListSep := ',';
fdOpenDocument : FListSep := ';'
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.SetExpression(const AValue: String);
begin
SetLocalizedExpression(FFormatSettings, AValue);
end;
procedure TsExpressionParser.SetLocalizedExpression(const AFormatSettings: TFormatSettings;
procedure TsExpressionParser.InternalSetExpression(ADialect: TsFormulaDialect;
const AValue: String);
begin
if FExpression = AValue then
exit;
FFormatSettings := AFormatSettings;
UpdateExprFormatSettings;
FExpression := AValue;
if (AValue <> '') and (AValue[1] = '=') then
FScanner.Source := Copy(AValue, 2, Length(AValue))
else
FScanner.Source := AValue;
Delete(FExpression, 1, 1);
SetDialect(ADialect);
FreeAndNil(FExprNode);
if (FExpression <> '') then
begin
FScanner.Source := FExpression;
if FExpression <> '' then begin
GetToken;
FExprNode := Level1;
if (TokenType <> ttEOF) then
ParserError(Format(rsUnterminatedExpression, [Scanner.Pos, CurrentToken]));
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)
@@ -2078,16 +2149,11 @@ 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);
var
oldDialect: TsFormulaDialect;
begin
oldDialect := FDialect;
PrepareCopyMode(ACell, ACell);
try
FDialect := fdExcelR1C1;
PrepareCopyMode(ACell, ACell);
Expression := AValue;
InternalSetExpression(fdExcelR1C1, AValue);
finally
FDialect := oldDialect;
PrepareCopyMode(nil, nil);
end;
end;
@@ -2554,7 +2620,7 @@ end;
function TsExprIdentifierDef.GetFormatSettings: TFormatSettings;
begin
Result := TsExprIdentifierDefs(Collection).Parser.FFormatSettings;
Result := TsExprIdentifierDefs(Collection).Parser.FFormatSettings^;
end;
function TsExprIdentifierDef.GetResultType: TsResultType;
@@ -3017,9 +3083,9 @@ 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 !!!
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);
rtFloat : Result := FloatToStr(FValue.ResFloat, Parser.FFormatSettings^);
rtError : Result := GetErrorValueStr(FValue.ResError);
end;
end;
@@ -4039,7 +4105,7 @@ begin
c := GetCol;
if Has3dLink then begin
case FParser.Dialect of
fdExcelA1:
fdExcelA1, fdLocalized:
Result := Format('%s!%s', [GetQuotedSheetName, GetCellString(r, c, FFlags)]);
fdExcelR1C1:
Result := Format('%s!%s', [GetQuotedSheetName,
@@ -4053,7 +4119,7 @@ begin
end
end else
case FParser.Dialect of
fdExcelA1:
fdExcelA1, fdLocalized:
Result := GetCellString(GetRow, GetCol, FFlags);
fdExcelR1C1:
Result := GetCellString_R1C1(GetRow, GetCol, FFlags, FParser.FSourceCell^.Row, FParser.FSourceCell^.Col);
@@ -4308,7 +4374,7 @@ begin
if F3dRange then
case FParser.Dialect of
fdExcelA1:
fdExcelA1, fdLocalized:
Result := GetCellRangeString(s1, s2, r1, c1, r2, c2, FFlags, true);
fdExcelR1C1:
Result := GetCellRangeString_R1C1(s1, s2, r1, c1, r2, c2, FFlags,
@@ -4322,7 +4388,7 @@ begin
end
else
case FParser.Dialect of
fdExcelA1:
fdExcelA1, fdLocalized:
Result := GetCellRangeString(r1, c1, r2, c2, FFlags, true);
fdExcelR1C1:
Result := GetCellRangeString_R1C1(r1, c1, r2, c2, FFlags,