mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-10-25 16:01:32 +02:00
* Allow hex, octal and binary notation for expression parser (patch from bug ID #33216)
git-svn-id: trunk@38326 -
This commit is contained in:
parent
4a0072d43d
commit
84377291b4
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user