* Allow hex, octal and binary notation for expression parser (patch from bug ID #33216)

git-svn-id: trunk@38326 -
This commit is contained in:
michael 2018-02-24 10:59:44 +00:00
parent 4a0072d43d
commit 84377291b4
2 changed files with 167 additions and 26 deletions

View File

@ -47,6 +47,8 @@ Type
TFPExprFunction = Class; TFPExprFunction = Class;
TFPExprFunctionClass = Class of TFPExprFunction; TFPExprFunctionClass = Class of TFPExprFunction;
TNumberKind = (nkDecimal, nkHex, nkOctal, nkBinary);
{ TFPExpressionScanner } { TFPExpressionScanner }
TFPExpressionScanner = Class(TObject) TFPExpressionScanner = Class(TObject)
@ -62,14 +64,14 @@ Type
protected protected
procedure SetSource(const AValue: String); virtual; procedure SetSource(const AValue: String); virtual;
function DoIdentifier: TTokenType; function DoIdentifier: TTokenType;
function DoNumber: TTokenType; function DoNumber(AKind: TNumberKind): TTokenType;
function DoDelimiter: TTokenType; function DoDelimiter: TTokenType;
function DoString: TTokenType; function DoString: TTokenType;
Function NextPos : Char; // inline; Function NextPos : Char; // inline;
procedure SkipWhiteSpace; // inline; procedure SkipWhiteSpace; // inline;
function IsWordDelim(C : Char) : Boolean; // inline; function IsWordDelim(C : Char) : Boolean; // inline;
function IsDelim(C : Char) : Boolean; // inline; function IsDelim(C : Char) : Boolean; // inline;
function IsDigit(C : Char) : Boolean; // inline; function IsDigit(C : Char; AKind: TNumberKind) : Boolean; // inline;
function IsAlpha(C : Char) : Boolean; // inline; function IsAlpha(C : Char) : Boolean; // inline;
public public
Constructor Create; Constructor Create;
@ -591,6 +593,7 @@ Type
TAggregateExpr = Class(TFPExprFunction) TAggregateExpr = Class(TFPExprFunction)
Protected Protected
FResult : TFPExpressionResult; FResult : TFPExpressionResult;
public
Class Function IsAggregate : Boolean; override; Class Function IsAggregate : Boolean; override;
Procedure GetNodeValue(var Result : TFPExpressionResult); override; Procedure GetNodeValue(var Result : TFPExpressionResult); override;
end; end;
@ -781,14 +784,23 @@ uses typinfo;
const const
cNull=#0; cNull=#0;
cSingleQuote = ''''; cSingleQuote = '''';
cHexIdentifier = '$';
cOctalIdentifier = '&';
cBinaryIdentifier = '%';
Digits = ['0'..'9','.']; Digits = ['0'..'9','.'];
HexDigits = ['0'..'9', 'A'..'F', 'a'..'f'];
OctalDigits = ['0'..'7'];
BinaryDigits = ['0', '1'];
WhiteSpace = [' ',#13,#10,#9]; WhiteSpace = [' ',#13,#10,#9];
Operators = ['+','-','<','>','=','/','*','^']; Operators = ['+','-','<','>','=','/','*','^'];
Delimiters = Operators+[',','(',')']; Delimiters = Operators+[',','(',')'];
Symbols = ['%']+Delimiters; Symbols = ['%']+Delimiters;
WordDelimiters = WhiteSpace + Symbols; WordDelimiters = WhiteSpace + Symbols;
var
FileFormatSettings: TFormatSettings;
Resourcestring Resourcestring
SBadQuotes = 'Unterminated string'; SBadQuotes = 'Unterminated string';
SUnknownDelimiter = 'Unknown delimiter character: "%s"'; SUnknownDelimiter = 'Unknown delimiter character: "%s"';
@ -1115,9 +1127,14 @@ begin
Result:=C in Delimiters; Result:=C in Delimiters;
end; end;
function TFPExpressionScanner.IsDigit(C: Char): Boolean; function TFPExpressionScanner.IsDigit(C: Char; AKind: TNumberKind): Boolean;
begin begin
Result:=C in Digits; case AKind of
nkDecimal: Result := C in Digits;
nkHex : Result := C in HexDigits;
nkOctal : Result := C in OctalDigits;
nkBinary : Result := C in BinaryDigits;
end;
end; end;
Procedure TFPExpressionScanner.SkipWhiteSpace; Procedure TFPExpressionScanner.SkipWhiteSpace;
@ -1215,7 +1232,21 @@ begin
Result:=#0; Result:=#0;
end; end;
Function TFPExpressionScanner.DoNumber : TTokenType; procedure Val(const S: string; out V: TExprFloat; out Code: Integer);
var
L64: Int64;
begin
if (S <> '') and (S[1] in ['&', '$', '%']) then
begin
System.Val(S, L64, Code);
if Code = 0 then
V := L64
end
else
System.Val(S, V, Code);
end;
Function TFPExpressionScanner.DoNumber(AKind: TNumberKind) : TTokenType;
Var Var
C : Char; C : Char;
@ -1223,16 +1254,38 @@ Var
I : Integer; I : Integer;
prevC: Char; prevC: Char;
function ValidDigit(C: Char; AKind: TNumberKind): Boolean;
begin
Result := IsDigit(C, AKind);
if (not Result) then
case AKind of
nkDecimal:
Result := ((FToken <> '') and (UpCase(C)='E')) or
((FToken <> '') and (C in ['+','-']) and (prevC='E'));
nkHex:
Result := (C = cHexIdentifier) and (prevC = #0);
nkOctal:
Result := (C = cOctalIdentifier) and (prevC = #0);
nkBinary:
Result := (C = cBinaryIdentifier) and (prevC = #0);
end;
end;
begin begin
C:=CurrentChar; C:=CurrentChar;
prevC := #0; prevC := #0;
while (not IsWordDelim(C) or (prevC in ['E','-','+'])) and (C<>cNull) do while (C <> cNull) do
begin begin
If Not ( IsDigit(C) if IsWordDelim(C) then
or ((FToken<>'') and (Upcase(C)='E')) case AKind of
or ((FToken<>'') and (C in ['+','-']) and (prevC='E')) nkDecimal:
) if not (prevC in ['E','-','+']) then break;
then nkHex, nkOctal:
break;
nkBinary:
if (prevC <> #0) then break; // allow '%' as first char
end;
if not ValidDigit(C, AKind) then
ScanError(Format(SErrInvalidNumberChar,[C])); ScanError(Format(SErrInvalidNumberChar,[C]));
FToken := FToken+C; FToken := FToken+C;
prevC := Upcase(C); prevC := Upcase(C);
@ -1306,8 +1359,14 @@ begin
Result:=DoDelimiter Result:=DoDelimiter
else if (C=cSingleQuote) then else if (C=cSingleQuote) then
Result:=DoString Result:=DoString
else if IsDigit(C) then else if (C=cHexIdentifier) then
Result:=DoNumber Result := DoNumber(nkHex)
else if (C=cOctalIdentifier) then
Result := DoNumber(nkOctal)
else if (C=cBinaryIdentifier) then
Result := DoNumber(nkBinary)
else if IsDigit(C, nkDecimal) then
Result:=DoNumber(nkDecimal)
else if IsAlpha(C) or (C='"') then else if IsAlpha(C) or (C='"') then
Result:=DoIdentifier Result:=DoIdentifier
else else
@ -2112,8 +2171,8 @@ begin
Case FValue.ResultType of Case FValue.ResultType of
rtBoolean : FValue.ResBoolean:=FStringValue='True'; rtBoolean : FValue.ResBoolean:=FStringValue='True';
rtInteger : FValue.ResInteger:=StrToInt(AValue); rtInteger : FValue.ResInteger:=StrToInt(AValue);
rtFloat : FValue.ResFloat:=StrToFloat(AValue); rtFloat : FValue.ResFloat:=StrToFloat(AValue, FileFormatSettings);
rtDateTime : FValue.ResDateTime:=StrToDateTime(AValue); rtDateTime : FValue.ResDateTime:=StrToDateTime(AValue, FileFormatSettings);
rtString : FValue.ResString:=AValue; rtString : FValue.ResString:=AValue;
end end
else else
@ -2223,8 +2282,8 @@ begin
else else
Result:='False'; Result:='False';
rtInteger : Result:=IntToStr(FValue.ResInteger); rtInteger : Result:=IntToStr(FValue.ResInteger);
rtFloat : Result:=FloatToStr(FValue.ResFloat); rtFloat : Result:=FloatToStr(FValue.ResFloat, FileFormatSettings);
rtDateTime : Result:=FormatDateTime('cccc',FValue.ResDateTime); rtDateTime : Result:=FormatDateTime('cccc',FValue.ResDateTime, FileFormatSettings);
rtString : Result:=FValue.ResString; rtString : Result:=FValue.ResString;
end; end;
end; end;
@ -4112,8 +4171,19 @@ begin
FCategory:=(Source as TFPBuiltInExprIdentifierDef).Category; FCategory:=(Source as TFPBuiltInExprIdentifierDef).Category;
end; end;
procedure InitFileFormatSettings;
begin
FileFormatSettings := DefaultFormatSettings;
FileFormatSettings.DecimalSeparator := '.';
FileFormatSettings.DateSeparator := '-';
FileFormatSettings.TimeSeparator := ':';
FileFormatsettings.ShortDateFormat := 'yyyy-mm-dd';
FileFormatSettings.LongTimeFormat := 'hh:nn:ss';
end;
initialization initialization
RegisterStdBuiltins(BuiltinIdentifiers); RegisterStdBuiltins(BuiltinIdentifiers);
InitFileFormatSettings;
finalization finalization
FreeBuiltins; FreeBuiltins;

View File

@ -530,6 +530,7 @@ type
private private
Published Published
Procedure TestCreate; Procedure TestCreate;
Procedure TestNumberValues;
Procedure TestSimpleNodeFloat; Procedure TestSimpleNodeFloat;
procedure TestSimpleNodeInteger; procedure TestSimpleNodeInteger;
procedure TestSimpleNodeBooleanTrue; procedure TestSimpleNodeBooleanTrue;
@ -1320,10 +1321,19 @@ procedure TTestExpressionScanner.TestTokens;
Const Const
TestStrings : Array[TTokenType] of String TestStrings : Array[TTokenType] of String
(*
TTokenType = (ttPlus, ttMinus, ttLessThan, ttLargerThan, ttEqual, ttDiv,
ttMod, ttMul, ttLeft, ttRight, ttLessThanEqual,
ttLargerThanEqual, ttunequal, ttNumber, ttString, ttIdentifier,
ttComma, ttAnd, ttOr, ttXor, ttTrue, ttFalse, ttNot, ttif,
ttCase, ttPower, ttEOF); // keep ttEOF last
*)
= ('+','-','<','>','=','/', = ('+','-','<','>','=','/',
'*','(',')','<=','>=', 'mod','*','(',')','<=',
'<>','1','''abc''','abc',',','and', '>=', '<>','1','''abc''','abc',
'or','xor','true','false','not','if','case','^',''); ',','and', 'or','xor','true','false','not',
'if','case','^','');
var var
t : TTokenType; t : TTokenType;
@ -1348,17 +1358,23 @@ end;
procedure TTestExpressionScanner.TestNumber; procedure TTestExpressionScanner.TestNumber;
begin begin
{TestString('123',ttNumber); TestString('123',ttNumber);
TestString('$FF',ttNumber);
TestString('&77',ttNumber);
TestString('%11111111',ttNumber);
TestString('123.4',ttNumber); TestString('123.4',ttNumber);
TestString('123.E4',ttNumber); TestString('123.E4',ttNumber);
TestString('1.E4',ttNumber); TestString('1.E4',ttNumber);
TestString('1e-2',ttNumber); TestString('1e-2',ttNumber);
DoInValidNumber('$GG');
DoInvalidNumber('&88');
DoInvalidNumber('%22');
DoInvalidNumber('1..1'); DoInvalidNumber('1..1');
}
DoInvalidNumber('1.E--1'); DoInvalidNumber('1.E--1');
// DoInvalidNumber('.E-1'); // DoInvalidNumber('.E-1');
end; end;
procedure TTestExpressionScanner.TestInvalidCharacter; procedure TTestExpressionScanner.TestInvalidCharacter;
begin begin
DoInvalidNumber('~'); DoInvalidNumber('~');
@ -2843,6 +2859,60 @@ begin
AssertEquals('No identifiers',0,FP.Identifiers.Count); AssertEquals('No identifiers',0,FP.Identifiers.Count);
end; end;
procedure TTestParserExpressions.TestNumberValues;
Procedure DoTest(E : String; V : integer);
var
res: TFPExpressionResult;
begin
FP.Expression:=E;
res := FP.Evaluate;
AssertTrue('Expression '+E+': Result is a number', Res.ResultType in [rtInteger,rtFloat]);
AssertTrue('Expression '+E+': Correct value', ArgToFloat(res)=V);
end;
begin
// Decimal numbers
DoTest('1', 1);
DoTest('1E2', 100);
DoTest('1.0/1E-2', 100);
// DoTest('200%', 2);
WriteLn;
// Hex numbers
DoTest('$0001', 1);
DoTest('-$01', -1);
DoTest('$A', 10);
DoTest('$FF', 255);
DoTest('$fe', 254);
DoTest('$FFFF', $FFFF);
DoTest('1E2', 100);
DoTest('$E', 14);
DoTest('$D+1E2', 113);
DoTest('$0A-$0B', -1);
// Hex and variables
FP.Identifiers.AddVariable('a', rtInteger, '1');
FP.Identifiers.AddVariable('b', rtInteger, '$B');
DoTest('a', 1);
DoTest('b', $B);
DoTest('$A+a', 11);
DoTest('$B-b', 0);
WriteLn;
// Octal numbers
DoTest('&10', 8);
DoTest('&10+10', 18);
// Mixed hex and octal expression
DoTest('&10-$0008', 0);
WriteLn;
// Binary numbers
DoTest('%1', 1);
DoTest('%11', 3);
DoTest('%1000', 8);
end;
procedure TTestParserExpressions.TestSimpleNodeFloat; procedure TTestParserExpressions.TestSimpleNodeFloat;
begin begin
@ -4343,7 +4413,7 @@ begin
AssertEquals('One variable added',1,FP.Identifiers.Count); AssertEquals('One variable added',1,FP.Identifiers.Count);
AssertSame('Result equals variable added',I,FP.Identifiers[0]); AssertSame('Result equals variable added',I,FP.Identifiers[0]);
AssertEquals('Variable has correct resulttype',rtDateTime,I.ResultType); AssertEquals('Variable has correct resulttype',rtDateTime,I.ResultType);
AssertEquals('Variable has correct value',FormatDateTime('cccc',D),I.Value); AssertEquals('Variable has correct value',FormatDateTime('yyyy-mm-dd hh:nn:ss',D),I.Value);
end; end;
procedure TTestParserVariables.AddVariabletwice; procedure TTestParserVariables.AddVariabletwice;
@ -5547,7 +5617,7 @@ begin
AssertSame('Result equals variable added',I,FM.Identifiers[0]); AssertSame('Result equals variable added',I,FM.Identifiers[0]);
AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category)); AssertEquals('Variable has correct category',ord(bcUser),Ord(I.Category));
AssertEquals('Variable has correct resulttype',rtDateTime,I.ResultType); AssertEquals('Variable has correct resulttype',rtDateTime,I.ResultType);
AssertEquals('Variable has correct value',FormatDateTime('cccc',D),I.Value); AssertEquals('Variable has correct value',FormatDateTime('yyyy-mm-dd hh:nn:ss',D),I.Value);
end; end;
procedure TTestBuiltinsManager.TestFunction1; procedure TTestBuiltinsManager.TestFunction1;
@ -5720,7 +5790,6 @@ procedure TTestBuiltins.TestRegister;
begin begin
RegisterStdBuiltins(FM); RegisterStdBuiltins(FM);
AssertEquals('Correct number of identifiers',69,FM.IdentifierCount);
Assertvariable('pi',rtFloat); Assertvariable('pi',rtFloat);
AssertFunction('cos','F','F',bcMath); AssertFunction('cos','F','F',bcMath);
AssertFunction('sin','F','F',bcMath); AssertFunction('sin','F','F',bcMath);
@ -5785,11 +5854,13 @@ begin
AssertFunction('strtotimedef','D','SD',bcConversion); AssertFunction('strtotimedef','D','SD',bcConversion);
AssertFunction('strtodatetime','D','S',bcConversion); AssertFunction('strtodatetime','D','S',bcConversion);
AssertFunction('strtodatetimedef','D','SD',bcConversion); AssertFunction('strtodatetimedef','D','SD',bcConversion);
AssertFunction('formatfloat','S','SF',bcConversion);
AssertFunction('sum','F','F',bcAggregate); AssertFunction('sum','F','F',bcAggregate);
AssertFunction('count','I','',bcAggregate); AssertFunction('count','I','',bcAggregate);
AssertFunction('avg','F','F',bcAggregate); AssertFunction('avg','F','F',bcAggregate);
AssertFunction('min','F','F',bcAggregate); AssertFunction('min','F','F',bcAggregate);
AssertFunction('max','F','F',bcAggregate); AssertFunction('max','F','F',bcAggregate);
AssertEquals('Correct number of identifiers',70,FM.IdentifierCount);
end; end;
procedure TTestBuiltins.TestVariablepi; procedure TTestBuiltins.TestVariablepi;