LazUtils, Masks: Turn define RANGES_AUTOREVERSE into a run time option. Add constructors for legacy syntax. More tests.

This commit is contained in:
Juha 2021-10-16 12:31:53 +03:00
parent d6a9516c11
commit 67111d9a30
3 changed files with 182 additions and 75 deletions

View File

@ -17,10 +17,6 @@ unit Masks;
{$mode objfpc}{$H+}
// RANGES_AUTOREVERSE
// If reverse ranges if needed, so range "[z-a]" is interpreted as "[a-z]"
{$DEFINE RANGES_AUTOREVERSE}
interface
uses
@ -148,12 +144,22 @@ const
MaskOpCodesDefaultAllowed=MaskOpCodesAllAllowed;
// Match [x] literally, not as a range.
// Leave out eMaskOpcodeAnyCharOrNone, eMaskOpcodeRange and eMaskOpcodeOptionalChar.
MaskOpCodesDisableRange=[eMaskOpcodeAnyChar,
eMaskOpcodeAnyText,
eMaskOpcodeNegateGroup,
eMaskOpcodeEscapeChar];
// Interpret [?] as literal question mark instead of 0..1 chars wildcard.
// Disable backslash escaping characters like "\?".
// Leave out eMaskOpcodeAnyCharOrNone and eMaskOpcodeEscapeChar
MaskOpCodesNoEscape=[eMaskOpcodeAnyChar,
eMaskOpcodeAnyText,
eMaskOpcodeRange,
eMaskOpcodeOptionalChar,
eMaskOpcodeNegateGroup];
type
// Backwards compatible options.
@ -173,6 +179,7 @@ type
procedure IncrementLastCounterBy(const aOpcode: TMaskOpCode; const aValue: integer);
protected
cCaseSensitive: Boolean;
cRangeAutoReverse: Boolean; // If enabled, range [z-a] is interpreted as [a-z]
cMaskIsCompiled: Boolean;
cMaskCompiled: TBytes;
cMaskCompiledIndex: integer;
@ -195,9 +202,11 @@ type
public
constructor Create(aCaseSensitive: Boolean=False;
aOpcodesAllowed: TMaskOpcodesSet=MaskOpCodesDefaultAllowed);
constructor CreateLegacy(aCaseSensitive: Boolean=False);
constructor Create(aOptions: TMaskOptions);
public
property CaseSensitive: Boolean read cCaseSensitive;
property RangeAutoReverse: Boolean read cRangeAutoReverse write cRangeAutoReverse;
property EscapeChar: Char read cMaskEscapeChar write SetMaskEscapeChar;
end;
@ -208,12 +217,14 @@ type
cMatchString: String;
// Used by Compile.
FMaskInd: Integer;
FCPLength: integer;
FLastOC: TMaskOpCode;
FCPLength: integer; // Size of codepoint.
FLastOC: TMaskOpCode; // Last OpCode.
FMask: String;
procedure AddAnyChar;
procedure AddLiteral;
procedure AddRange;
procedure CompileRange;
procedure ReverseRange;
protected
cOriginalMask: String;
class function CompareUTF8Sequences(const P1,P2: PChar): integer; static;
@ -221,7 +232,9 @@ type
public
constructor Create(const aMask: String; aCaseSensitive: Boolean=False;
aOpcodesAllowed: TMaskOpcodesSet=MaskOpCodesDefaultAllowed);
constructor CreateLegacy(const aMask: String; aCaseSensitive: Boolean);
constructor Create(const aMask: String; aOptions: TMaskOptions);
deprecated 'Use CreateLegacy or Create with other params.'; // in Lazarus 2.3.
procedure Compile; override;
function Matches(const aStringToMatch: String): Boolean; virtual;
@ -292,7 +305,9 @@ type
function MatchesMask(const FileName, Mask: String; CaseSensitive: Boolean=False;
aOpcodesAllowed: TMaskOpcodesSet=MaskOpCodesDefaultAllowed): Boolean;
function MatchesMaskLegacy(const FileName, Mask: String; CaseSensitive: Boolean=False): Boolean;
function MatchesMask(const FileName, Mask: String; Options: TMaskOptions): Boolean;
deprecated 'Use MatchesMaskLegacy or MatchesMask with other params.'; // in Lazarus 2.3.
function MatchesWindowsMask(const FileName, Mask: String; CaseSensitive: Boolean=False;
aOpcodesAllowed: TMaskOpcodesSet=MaskOpCodesDefaultAllowed;
@ -304,22 +319,25 @@ function MatchesMaskList(const FileName, Mask: String; Separator: Char=';';
aOpcodesAllowed: TMaskOpcodesSet=MaskOpCodesDefaultAllowed): Boolean;
function MatchesMaskList(const FileName, Mask: String; Separator: Char;
Options: TMaskOptions): Boolean;
deprecated 'Use MatchesMaskList with other params.'; // in Lazarus 2.3.
function MatchesWindowsMaskList(const FileName, Mask: String; Separator: Char=';';
CaseSensitive: Boolean=False;
aOpcodesAllowed: TMaskOpcodesSet=MaskOpCodesDefaultAllowed): Boolean;
function MatchesWindowsMaskList(const FileName, Mask: String; Separator: Char;
Options: TMaskOptions): Boolean;
deprecated 'Use MatchesWindowsMaskList with other params.'; // in Lazarus 2.3.
implementation
function EncodeDisableRange(Options: TMaskOptions): TMaskOpcodesSet;
// Encode the Disable Range option from legacy TMaskOptions.
begin
if moDisableSets in Options then
Result:=MaskOpCodesDisableRange
else
Result:=MaskOpCodesDefaultAllowed;
else // Disable '\' escaping for legacy code.
Result:=MaskOpCodesNoEscape; //MaskOpCodesDefaultAllowed;
end;
function MatchesMask(const FileName, Mask: String; CaseSensitive: Boolean;
@ -335,9 +353,16 @@ begin
end;
end;
function MatchesMaskLegacy(const FileName, Mask: String; CaseSensitive: Boolean): Boolean;
// Use [?] syntax for literal '?', no escaping chars with '\'.
begin
Result := MatchesMask(FileName, Mask, CaseSensitive, MaskOpCodesNoEscape);
end;
function MatchesMask(const FileName, Mask: String; Options: TMaskOptions): Boolean;
begin
MatchesMask(FileName, Mask, moCaseSensitive in Options, EncodeDisableRange(Options));
Result := MatchesMask(FileName, Mask, moCaseSensitive in Options,
EncodeDisableRange(Options));
end;
function MatchesWindowsMask(const FileName, Mask: String; CaseSensitive: Boolean;
@ -355,7 +380,8 @@ end;
function MatchesWindowsMask(const FileName, Mask: String; Options: TMaskOptions): Boolean;
begin
MatchesWindowsMask(FileName, Mask, moCaseSensitive in Options, EncodeDisableRange(Options));
Result := MatchesWindowsMask(FileName, Mask, moCaseSensitive in Options,
EncodeDisableRange(Options));
end;
function MatchesMaskList(const FileName, Mask: String; Separator: Char;
@ -374,8 +400,8 @@ end;
function MatchesMaskList(const FileName, Mask: String; Separator: Char;
Options: TMaskOptions): Boolean;
begin
MatchesMaskList(FileName, Mask, Separator, moCaseSensitive in Options,
EncodeDisableRange(Options));
Result := MatchesMaskList(FileName, Mask, Separator, moCaseSensitive in Options,
EncodeDisableRange(Options));
end;
function MatchesWindowsMaskList(const FileName, Mask: String; Separator: Char;
@ -394,8 +420,8 @@ end;
function MatchesWindowsMaskList(const FileName, Mask: String; Separator: Char;
Options: TMaskOptions): Boolean;
begin
MatchesWindowsMaskList(FileName, Mask, Separator, moCaseSensitive in Options,
EncodeDisableRange(Options));
Result := MatchesWindowsMaskList(FileName, Mask, Separator, moCaseSensitive in Options,
EncodeDisableRange(Options));
end;
{ EMaskError }
@ -506,9 +532,16 @@ constructor TMaskBase.Create(aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpco
begin
cCaseSensitive:=aCaseSensitive;
cMaskOpcodesAllowed:=aOpcodesAllowed;
cRangeAutoReverse:=True;
cMaskEscapeChar:='\';
end;
constructor TMaskBase.CreateLegacy(aCaseSensitive: Boolean);
// Use [?] syntax for literal '?', no escaping chars with '\'.
begin
Create(aCaseSensitive, MaskOpCodesNoEscape);
end;
constructor TMaskBase.Create(aOptions: TMaskOptions);
begin
Create(moCaseSensitive in aOptions, EncodeDisableRange(aOptions));
@ -535,6 +568,22 @@ begin
FLastOC:=TMaskOpCode.Literal;
end;
procedure TMaskUTF8.AddRange;
begin
Add(FCPLength,@FMask[FMaskInd]);
inc(FMaskInd,FCPLength+1); // Add 1 for "-"
FCPLength:=UTF8CodepointSizeFast(@FMask[FMaskInd]);
Add(FCPLength,@FMask[FMaskInd]);
end;
procedure TMaskUTF8.ReverseRange;
begin
Add(UTF8CodepointSizeFast(@FMask[FMaskInd+FCPLength+1]),@FMask[FMaskInd+FCPLength+1]);
Add(FCPLength,@FMask[FMaskInd]);
inc(FMaskInd,FCPLength+1);
FCPLength:=UTF8CodepointSizeFast(@FMask[FMaskInd]);
end;
procedure TMaskUTF8.CompileRange;
var
lCharsGroupInsertSize: integer;
@ -596,26 +645,11 @@ begin
// Check if it is a range
Add(TMaskOpCode.Range);
// Check if reverse range is needed
{$IFDEF RANGES_AUTOREVERSE}
if CompareUTF8Sequences(@FMask[FMaskInd],@FMask[FMaskInd+FCPLength+1])<0 then begin
Add(FCPLength,@FMask[FMaskInd]);
inc(FMaskInd,FCPLength);
inc(FMaskInd,1); // The "-"
FCPLength:=UTF8CodepointSizeFast(@FMask[FMaskInd]);
Add(FCPLength,@FMask[FMaskInd]);
end else begin
Add(UTF8CodepointSizeFast(@FMask[FMaskInd+FCPLength+1]),@FMask[FMaskInd+FCPLength+1]);
Add(FCPLength,@FMask[FMaskInd]);
inc(FMaskInd,FCPLength+1);
FCPLength:=UTF8CodepointSizeFast(@FMask[FMaskInd]);
end;
{$ELSE}
Add(lCPLength,@lMask[j]);
inc(j,lCPLength);
inc(j,1); // The "-"
lCPLength:=UTF8CodepointSizeFast(@lMask[j]);
Add(lCPLength,@lMask[j]);
{$ENDIF}
if (not cRangeAutoReverse)
or (CompareUTF8Sequences(@FMask[FMaskInd],@FMask[FMaskInd+FCPLength+1])<0) then
AddRange
else
ReverseRange;
FLastOC:=TMaskOpCode.Range;
end else if FMask[FMaskInd]=']' then begin
@ -883,6 +917,12 @@ begin
cOriginalMask:=aMask;
end;
constructor TMaskUTF8.CreateLegacy(const aMask: String; aCaseSensitive: Boolean);
// Use [?] syntax for literal '?', no escaping chars with '\'.
begin
Create(aMask, aCaseSensitive, MaskOpCodesNoEscape);
end;
constructor TMaskUTF8.Create(const aMask: String; aOptions: TMaskOptions);
begin
inherited Create(aOptions);

View File

@ -22,14 +22,57 @@
</VersionInfo>
<BuildModes>
<Item Name="default" Default="True"/>
<Item Name="Debug">
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Parsing>
<SyntaxOptions>
<IncludeAssertionCode Value="True"/>
</SyntaxOptions>
</Parsing>
<CodeGeneration>
<Checks>
<IOChecks Value="True"/>
<RangeChecks Value="True"/>
<OverflowChecks Value="True"/>
<StackChecks Value="True"/>
</Checks>
<VerifyObjMethodCallValidity Value="True"/>
</CodeGeneration>
<Linking>
<Debugging>
<DebugInfoType Value="dsDwarf3"/>
<UseHeaptrc Value="True"/>
<TrashVariables Value="True"/>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions>
</Item>
<Item Name="Release">
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<CodeGeneration>
<SmartLinkUnit Value="True"/>
<Optimizations>
<OptimizationLevel Value="3"/>
</Optimizations>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="False"/>
</Debugging>
<LinkSmart Value="True"/>
</Linking>
</CompilerOptions>
</Item>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<local>
<LaunchingApplication PathPlusParams="\usr\X11R6\bin\xterm -T &apos;Lazarus Run Output&apos; -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/>
</local>
<FormatVersion Value="2"/>
<Modes>
<Mode Name="default">

View File

@ -35,8 +35,9 @@ type
procedure Test;
procedure TestMask(const S, Mask: String; Result: Boolean);
procedure TestMaskDisableRange(const S, Mask: String; Result: Boolean);
procedure TestMaskLegacy(const S, Mask: String; Result: Boolean);
procedure TestMaskWindows(const S, Mask: String; Result: Boolean);
procedure TestMaskException(const S, Mask: String; AFail: Boolean);
procedure TestWindowsMask(const S, Mask: String; Result: Boolean);
published
procedure TestMaskSyntax;
procedure TestNil;
@ -45,6 +46,7 @@ type
procedure TestCharSet;
procedure TestDisableRange;
procedure TestCase;
procedure TestLegacy;
procedure TestSpecial;
procedure TestWindows;
end;
@ -54,6 +56,27 @@ begin
MatchesMask(FS, FMask);
end;
procedure TTestMask.TestMask(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result, MatchesMask(S, Mask));
end;
procedure TTestMask.TestMaskDisableRange(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result,
MatchesMask(S, Mask, False, MaskOpCodesDisableRange));
end;
procedure TTestMask.TestMaskLegacy(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result, MatchesMaskLegacy(S, Mask));
end;
procedure TTestMask.TestMaskWindows(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result, MatchesWindowsMask(S, Mask));
end;
procedure TTestMask.TestMaskException(const S, Mask: String; AFail: Boolean);
begin
FS := S;
@ -96,17 +119,6 @@ begin
//TestMaskException('', '[--a]', True);
end;
procedure TTestMask.TestMask(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result, MatchesMask(S, Mask));
end;
procedure TTestMask.TestMaskDisableRange(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result,
MatchesMask(S, Mask, False, MaskOpCodesDisableRange));
end;
procedure TTestMask.TestNil;
begin
TestMask('', '', True);
@ -218,6 +230,7 @@ begin
TestMask('c', '[!b]', True);
TestMask('c', '[a-c]', True);
TestMask('c', '[a-d]', True);
TestMask('c', '[d-a]', True); // Reverse range
TestMask('c', '[!a-b]', True);
TestMask('c', '[abc]', True);
@ -231,6 +244,7 @@ begin
TestMask('c', '[a]', False); // ASCII
TestMask('c', '[!c]', False);
TestMask('c', '[a-b]', False);
TestMask('c', '[z-d]', False); // Reverse range
TestMask('c', '[abd]', False);
TestMask('ö', '[ä]', False); // Unicode
@ -258,6 +272,18 @@ begin
TestMask('abcÖ', '*[äũö]', True);
end;
procedure TTestMask.TestLegacy;
begin
TestMaskLegacy('a?c', '?[?]?', True);
TestMaskLegacy('C:\x', 'C:\x', True);
TestMaskLegacy('a?c', '?\??', False);
TestMaskLegacy('ab*.x', '??\*.x', False);
TestMaskLegacy('x \ y', '? \\ ?', False);
TestMaskLegacy('abc', '?[?]?', False);
TestMaskLegacy('a??d', '?[?]?', False);
end;
procedure TTestMask.TestSpecial;
begin
TestMask('a?c', '?[?]?', True);
@ -267,40 +293,38 @@ begin
TestMask('ab*.x', '??\*.x', True);
TestMask('a[c]d', '?\[*', True);
TestMask('x \ y', '? \\ ?', True);
TestMask('abcd', 'a[??]d', True);
TestMask('abd', 'a[??]d', True);
TestMask('ad', 'a[??]d', True);
TestMask('C:\x', 'C:\x', False);
TestMask('abcd', '?[?]?', False);
end;
procedure TTestMask.TestWindowsMask(const S, Mask: String; Result: Boolean);
begin
AssertEquals(S + ' match ' + Mask + ': ', Result, MatchesWindowsMask(S, Mask));
end;
procedure TTestMask.TestWindows;
begin
TestWindowsMask('abc.txt', '*.*', True);
TestWindowsMask('abc', '*.*', True);
TestWindowsMask('abc.txt', '*', True);
TestWindowsMask('abc', '*', True);
TestWindowsMask('abc', '*.', True);
TestWindowsMask('abcd.txt', 'abc???.*', True);
TestWindowsMask('abcd.txt', 'abc???.txt?', True);
TestWindowsMask('abcd.txt', 'abc*', True);
TestWindowsMask('abc.pas.bak', '*.bak', True);
TestWindowsMask('C:\x', 'C:\x', True);
TestWindowsMask('C:\ab[c]d', 'C:*[*]*', True);
TestWindowsMask('', '*', True);
TestWindowsMask('', '?', True);
TestMaskWindows('abc.txt', '*.*', True);
TestMaskWindows('abc', '*.*', True);
TestMaskWindows('abc.txt', '*', True);
TestMaskWindows('abc', '*', True);
TestMaskWindows('abc', '*.', True);
TestMaskWindows('abcd.txt', 'abc???.*', True);
TestMaskWindows('abcd.txt', 'abc???.txt?', True);
TestMaskWindows('abcd.txt', 'abc*', True);
TestMaskWindows('abc.pas.bak', '*.bak', True);
TestMaskWindows('C:\x', 'C:\x', True);
TestMaskWindows('C:\ab[c]d', 'C:*[*]*', True);
TestMaskWindows('', '*', True);
TestMaskWindows('', '?', True);
TestWindowsMask('abcd.txt', '*.txtx', False);
TestWindowsMask('abc.txt', '*.', False);
TestWindowsMask('abc.txt', '.*', False);
TestWindowsMask('abc.pas.bak', '*.pas', False);
TestWindowsMask('abc', '.*', False);
TestWindowsMask('x \ y', '? \\ ?', False);
TestWindowsMask('', 'a', False);
TestWindowsMask('', '[a]', False);
TestMaskWindows('abcd.txt', '*.txtx', False);
TestMaskWindows('abc.txt', '*.', False);
TestMaskWindows('abc.txt', '.*', False);
TestMaskWindows('abc.pas.bak', '*.pas', False);
TestMaskWindows('abc', '.*', False);
TestMaskWindows('x \ y', '? \\ ?', False);
TestMaskWindows('', 'a', False);
TestMaskWindows('', '[a]', False);
end;
begin