Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2015-02

Record helpers on built in types?

27 messages · 6 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Anthony Walter

I tried this out today on FPC fixes_3_0 and was surprised to see it worked:

type
  StringHelper = record helper for string
  private
    function GetLength: Integer;
  procedure SetLength(Value: Integer);
    function GetIsEmpty: Boolean;
    function GetIsWhitespace: Boolean;
  public
    function BeginsWith(const S: string): Boolean;
    function EndsWith(const S: string): Boolean;
    function Trim: string;
    property Length: Integer read GetLength write SetLength;
    property IsEmpty: Boolean read GetIsEmpty;
    property IsWhitespace: Boolean read GetIsWhitespace;
  end;

I love this and would like to know if this is going to be an officially supported feature and can I depend on it to work going forward?

I wrote a string library which is maybe 10x faster for searching, replacing, trimming strings than the default RTL functions and was considering moving them and other add-ons to record helper properties and methods as above.

Reply by email

Anthony Walter
to Anthony Walter

I hit send before doing more research. I found this:

http://wiki.freepascal.org/Helper_types#Syntax

"In theory they could be extended to support interfaces, objects or even primitive types as well (although the latter would need bigger adjustments inside the compiler).".

If would seem as if the latter, a big adjustment, was already made. Is it official then?

Also, what are "[hint modifiers];" at the end of the syntax listing? Could I get a few examples of hint modifiers of a class/record helper?

Reply by email

Anthony Walter
to Anthony Walter

More information.

It seems code tools doesn't understand class or record helpers when it come to code insight.

If I type "MyString." and press alt space, I actually receive a code tools "Illegal qualifier . found" error and the entire line highlights red.

Interestingly, if I define a "TPointHelper = record record helper for TPoint", type "MyPoint." and press alt space nothing happens. I get no insight into my extension proeprties and methods, but I don't get a big honking red error line.

If the code insight person could implement proper insight on helpers, I'd be so grateful! Thanks.

Reply by email

silvioprog
to Anthony Walter
Anthony Walter wrote 16 lines
More information. It seems code tools doesn't understand class or record helpers when it come to code insight. If I type "MyString." and press alt space, I actually receive a code tools "Illegal qualifier . found" error and the entire line highlights red. Interestingly, if I define a "TPointHelper = record record helper for TPoint", type "MyPoint." and press alt space nothing happens. I get no insight into my extension proeprties and methods, but I don't get a big honking red error line. If the code insight person could implement proper insight on helpers, I'd be so grateful! Thanks.

Related.

http://lists.lazarus.freepascal.org/pipermail/lazarus/2014-November/089619.html

http://lists.lazarus.freepascal.org/pipermail/lazarus/2014-November/089622.html

http://lists.lazarus.freepascal.org/pipermail/lazarus/2014-November/089624.html

http://bugs.freepascal.org/view.php?id=27161

http://bugs.freepascal.org/view.php?id=22431

http://bugs.freepascal.org/view.php?id=27160

http://bugs.freepascal.org/view.php?id=27193

Silvio Clécio My public projects - github.com/silvioprog

Reply by email

Anthony Walter
to silvioprog

If anyone was interested in the subject, here is what I came up with after merging my string centric functions into record helpers:

type
  StringArrayHelper = record helper for StringArray
    { Join a string array into a string using a separator }
    function Join(const Separator: string): string;
  end

{ IntArrayHelper }

  IntArrayHelper = record helper for StringArray
    { Join an int array into a string using a separator }
    function Join(const Separator: string): string;
  end

{ StringHelper }

  StringHelper = record helper for string
  private
    function GetIsEmpty: Boolean;
    function GetIsWhitespace: Boolean;
    function GetIsIdentifier: Boolean;
    function GetIsAttribute: Boolean;
    function GetLength: Integer;
    procedure SetLength(Value: Integer);
  public
    { Repeat a character a given length a into string }
    procedure CharInto(C: Char; Len: Integer);
    { Copy a memory buffer into string }
    procedure CopyInto(P: Pointer; Len: Integer);
    { Inserts a substring at a position into string }
    procedure InsertInto(const SubStr: string; Position: Integer);
    { Returns true if a string matches a case insensitive value }
    function Equals(const Value: string): Boolean; overload;
    { Returns true if a string matches any in a set of case insensitive
values }
    function Equals(const Values: array of string): Boolean; overload;
    { Compares two strings optionally ignoring case returning -1 if string
comes before
      before value, 1 if string comes after value, ord 0 if string and
value are equal }
    function Compare(const Value: string; IgnoreCase: Boolean = False):
Integer;
    { Convert a string to uppercase }
    function ToUpper: string;
    { Convert a string to lowercase }
    function ToLower: string;
    { Copies a substring given a start and length }
    function Copy(Start: Integer; Len: Integer = 0): string;
    { Searches a string for a substring optionally ignoring case }
    function IndexOf(const SubStr: string; IgnoreCase: Boolean = False):
Integer; overload;
    { Searches a string for a substring from a start position optionally
ignoring case }
    function IndexOf(const SubStr: string; Start: Integer; IgnoreCase:
Boolean = False): Integer; overload;
    { Returns the number of a substring matches within a string }
    function MatchCount(const SubStr: string; IgnoreCase: Boolean = False):
Integer;
    { Returns an array of indices of a substring matches within a string }
    function MatchIndices(const SubStr: string; IgnoreCase: Boolean =
False): IntArray;
    { Replaces every instance of a pattern in a string }
    function Replace(const OldPattern, NewPattern: string; IgnoreCase:
Boolean = False): string;
    { Replaces the first instance of a pattern in a string }
    function ReplaceOne(const OldPattern, NewPattern: string; IgnoreCase:
Boolean = False): string;
    { Replaces everything aftger the first instance of a pattern in a
string }
    function ReplaceAfter(const OldPattern, NewPattern: string; IgnoreCase:
Boolean = False): string;
    { Trims white space from both sides of a string }
    function Trim: string;
    { Returns the index of a string in a string array or -1 if there is no
match }
    function ArrayIndex(const Values: array of string): Integer;
    { Splits a string into a string array using a separator }
    function Split(Separator: string): StringArray;
    { Splits a string into a int array using a separator }
    function SplitInt(const Separator: string): IntArray;
    { Splits a string into a int64 array using a separator }
    function SplitInt64(const S, Separator: string): Int64Array;
    { Returns the first subsection of a string if it were split using a
separator }
    function FirstOf(Separator: string): string;
    { Returns the second subsection of a string if it were split using a
separator }
    function SecondOf(Separator: string): string;
    { Returns the last subsection of a string if it were split using a
separator }
    function LastOf(Separator: string): string;
    { Search a string for a substring optionally ignoring case }
    function Contains(SubStr: string; IgnoreCase: Boolean = False): Boolean;
    { Returns true if a string begins with a substring while optionally
ignoring case }
    function BeginsWith(SubStr: string; IgnoreCase: Boolean = False):
Boolean;
    { Returns true if a string end with a substring while optionally
ignoring case }
    function EndsWith(SubStr: string; IgnoreCase: Boolean = False): Boolean;
    { Returns a string made to fit a given length padded on the left with a
character }
    function PadLeft(C: Char; Len: Integer): string;
    { Returns a string made to fit a given length padded on the right with
a character }
    function PadRight(C: Char; Len: Integer): string;
    { Returns a string surrounded by quotes if it contains whitespace }
    function Quote: string;
    { Analyze a string and find its line break style }
    function LineBreakStyle: TTextLineBreakStyle;
    { Converts the line break style of a string to a the desired style }
    function AdjustLineBreaks(Style: TTextLineBreakStyle): string; overload;
    { Converts the line break style of a string to the system preferred
defined style }
    function AdjustLineBreaks: string; overload;
    { Gets true if a string contains only whitespace characters }
    property IsEmpty: Boolean read GetIsEmpty;
    { Gets true if a string contains only whitespace characters }
    property IsWhitespace: Boolean read GetIsWhitespace;
    { Gets true if a string matches to rules of an identifier }
    property IsIdentifier: Boolean read GetIsIdentifier;
    { Gets true if a string matches to rules of an attribute }
    property IsAttribute: Boolean read GetIsAttribute;
    {  Gets or sets the length allocated for the string }
    property Length: Boolean read GetLength write SetLength;
  end;

Reply by email

silvioprog
to Anthony Walter
Anthony Walter wrote
If anyone was interested in the subject, here is what I came up with after merging my string centric functions into record helpers:

[...]

Is it similar to the Delphi XE's StringHelper? If yes, could you send a patch to the bugtracker?

Silvio Clécio My public projects - github.com/silvioprog

Reply by email

Anthony Walter
to silvioprog: [...] Is it similar to the Delphi XE's StringHelper? If yes, could you send a patch to…

I wasn't aware of XE's string helper, so no.

But if anyone wants to test my StrFindBuffer and StrFindBufferI to gauge their performance against similar RTL library routines, I believe you'll find they are significantly faster. As a consequence all other dependent functions (IndexOf, Match, Replace, Split, and more) would be significantly faster as well.

Here are copies of the relevant code:

http://www.getlazarus.org/helpers/string/

Reply by email

Dmitry Boyarintsev
to Anthony Walter: I wasn't aware of XE's string helper, so no. But if anyone wants to test my StrFindBuffer…
Anthony Walter wrote 9 lines
But if anyone wants to test my StrFindBuffer and StrFindBufferI to gauge their performance against similar RTL library routines, I believe you'll find they are significantly faster. As a consequence all other dependent functions (IndexOf, Match, Replace, Split, and more) would be significantly faster as well. Here are copies of the relevant code: http://www.getlazarus.org/helpers/string/

I see, you've implemented Boyer–Moore–Horspool algorithm for the string search.

Why don't you take part in patch-fest then?
http://bugs.freepascal.org/view.php?id=26864

Reply by email

silvioprog
to Anthony Walter: I wasn't aware of XE's string helper, so no. But if anyone wants to test my StrFindBuffer…
Anthony Walter wrote 12 lines
I wasn't aware of XE's string helper, so no. But if anyone wants to test my StrFindBuffer and StrFindBufferI to gauge their performance against similar RTL library routines, I believe you'll find they are significantly faster. As a consequence all other dependent functions (IndexOf, Match, Replace, Split, and more) would be significantly faster as well. Here are copies of the relevant code: http://www.getlazarus.org/helpers/string/

Very nice!

What is the license of it? I'll test it soon.

Any way, thanks for share it! =)

P.S.: IMHO you could send some patch to FPC team check it too.

Silvio Clécio My public projects - github.com/silvioprog

Reply by email

silvioprog
to Anthony Walter: Feel free to submit a patch for me, given the code I've posted. That is, after you test…
Anthony Walter wrote
Feel free to submit a patch for me, given the code I've posted. That is, after you test and validate the speed gains.

Dmitry?

Show quoted text 5 lines
I am busy finishing up this project (http://www.codebot.org/snapshop/) on Linux and Mac at the moment, and then creating a new video walk through (my prior one http://www.getlazarus.org/videos/reports/). This time I'll have access to some professional voice over work.

Saved in my favorites! =) Congratulations, your site and your projects are very good.

I'm working in custfcgi unit to send a patch to bugtracker, but after that I'll see your project. Please don't forget to send a patch - you are the best guy to do it, because you is the author hehe. =D.

Silvio Clécio My public projects - github.com/silvioprog

Reply by email

Dmitry Boyarintsev
to silvioprog
silvioprog wrote 8 lines
On Wed, Feb 18, 2015 at 1:24 AM, Anthony Walter wrote: > Feel free to submit a patch for me, given the code I've posted. That is, > after you test and validate the speed gains. > Dmitry?

I'm not interested.

thanks,
Dmitry

Reply by email

Sven Barth
to Anthony Walter: I tried this out today on FPC fixes_3_0 and was surprised to see it worked: type…
Anthony Walter wrote 20 lines
I tried this out today on FPC fixes_3_0 and was surprised to see it worked: type StringHelper = record helper for string private function GetLength: Integer; procedure SetLength(Value: Integer); function GetIsEmpty: Boolean; function GetIsWhitespace: Boolean; public function BeginsWith(const S: string): Boolean; function EndsWith(const S: string): Boolean; function Trim: string; property Length: Integer read GetLength write SetLength; property IsEmpty: Boolean read GetIsEmpty; property IsWhitespace: Boolean read GetIsWhitespace; end; I love this and would like to know if this is going to be an officially supported feature and can I depend on it to work going forward?

http://wiki.freepascal.org/FPC_New_Features_Trunk#Support_for_type_helpers

Please note that if you use "String" the types the helper will apply to will depend on whatever "String" currently is. Maybe it would be better to explicitely use "AnsiString" and "UnicodeString" to avoid potential surprises.
And just in case: type helpers might also be used on constants:

=== code begin ===

Writeln('Test'.Length);

=== code end ===

Regards,
Sven

Reply by email

Mattias Gaertner
to Anthony Walter: Dmitry: See this function and test if against a large block of text doing a case…
Show quoted text 5 lines
Dmitry: See this function and test if against a large block of text doing a case insensitive replace on a moderate length phrase. Match it in speed against whatever is in the RTL and see if my function is faster and by how much.

For speed reasons your functions should call the RTL functions for short strings. They are optimized for that.

Mattias

Reply by email

leledumbo
to Anthony Walter: Sven, thanks for all these improvements you've made. From this page…
can you explain what [hint modifiers] are

probably: http://www.freepascal.org/docs-html/ref/refse5.html

how does the optional (BaseHelper) come into play?

http://wiki.freepascal.org/Helper_types#Inheritance

View this message in context: http://free-pascal-general.1045716.n5.nabble.com/Record-helpers-on-built-in-types-tp5721002p5721025.html Sent from the Free Pascal - General mailing list .

Reply by email

Sven Barth
to Anthony Walter: Sven, thanks for all these improvements you've made. From this page…
"Anthony Walter" wrote
Sven, thanks for all these improvements you've made. From this page

http://wiki.freepascal.org/Helper_types#Syntax can you explain what [hint modifiers] are, and also how does the optional (BaseHelper) come into play?

Hint modifiers are the usual "deprecated", "platform", etc. modifiers as documented here:
http://www.freepascal.org/docs-html/ref/refse5.html#x17-160001.5

Base helper is if you want to extend an existing helper for the same type (or in case of class helpers for the parent type). This is useful, because currently only one helper for each type is active in the current scope. It's basically inheritance for helpers. The Delphi modes however support this only for class helpers.

Regards,
Sven

Reply by email

Anthony Walter
to Sven Barth

Okay guys, some testing results of my earlier referenced string functions. I went to this page and copied Moby Dick:

https://www.gutenberg.org/files/2701/2701-h/2701-h.htm

I save that to 'c:\temp\moby-dick.txt' and ran this test (IStopwatch.Time is seconds elapsed as Double):

procedure TForm1.Button1Click(Sender: TObject);
var
  Stopwatch: IStopwatch;
  B: Boolean;
  S, S1, S2: string;
  T1, T2: Double;
  I: Integer;
begin
  Stopwatch := StopwatchCreate;
  Memo1.Lines.Clear;
  S := FileReadStr('c:\temp\moby-dick.txt');
  Memo1.Lines.AddFormat('Length of moby dick text = {0}', [S.Length]);
  Memo1.Lines.AddLine;
  Stopwatch.Reset;
  S1 := S.Replace('Whale', 'The Big Fish', True);
  Stopwatch.Calculate;
  Stopwatch.Calculate;
  T1 := Stopwatch.Time;
  Memo1.Lines.AddFormat('StringHelper.Replace time = {0}', [T1]);
  Stopwatch.Reset;
  S2 := StringReplace(S, 'Whale', 'The Big Fish', [rfReplaceAll,
rfIgnoreCase]);
  Stopwatch.Calculate;
  T2 := Stopwatch.Time;
  Memo1.Lines.AddFormat('StringReplace time = {0}', [T2]);
  Memo1.Lines.Add('----------------------------');
  Memo1.Lines.AddFormat('Speed increase: {0:2} x faster', [T2 / T1]);
  Memo1.Lines.AddLine;
  Stopwatch.Reset;
  B := S1.Compare(S2) = 0;
  Stopwatch.Calculate;
  T1 := Stopwatch.Time;
  if B then
  Memo1.Lines.AddFormat('StringHelper.Compare time = {0}', [T1]);
  Stopwatch.Reset;
  B := AnsiCompareText(S1, S2) = 0;
  Stopwatch.Calculate;
  T2 := Stopwatch.Time;
  if B then
  Memo1.Lines.AddFormat('AnsiCompareText time = ', [T2]);
  Memo1.Lines.Add('----------------------------');
  Memo1.Lines.AddFormat('Speed increase: {0:2} x faster', [T2 / T1]);
  Memo1.Lines.AddLine;
  Stopwatch.Reset;
  I := S.MatchCount('Whale', True);
  Stopwatch.Calculate;
  Memo1.Lines.AddFormat('StringHelper.MatchCount = {0}', [I]);
  Memo1.Lines.AddFormat('StringHelper.MatchCount time = {0}',
[Stopwatch.Time]);
end;

Here are the results:

Length of moby dick text = 1239706

StringHelper.Replace time = 0.0189264156797435
StringReplace time = 0.810222176252864
----------------------------
Speed increase: 42.80 x faster

StringHelper.Compare time = 0.00125797372311354
AnsiCompareText time = 0.010209045431111
----------------------------
Speed increase: 8.11 x faster

StringHelper.MatchCount = 1705
StringHelper.MatchCount time = 0.00554488154011779

Reply by email

Anthony Walter
to Anthony Walter: I tried this out today on FPC fixes_3_0 and was surprised to see it worked: type…

I had a request. It would be nice if we could use class helpers with generic types like so:

type
  TArray<T> = array of T;
  StringArray = TArray<string>;
  IntArray = TArray<Integer>;
type
  TArrayHelper<T> = record helper for TArray<T>
  private
    function GetLength: Integer;
    procedure SetLength(Value: Integer);
  public
    procedure Push(const Value: T);
    function Pop: T;
    property Length: Integer read GetLength write SetLength;
  end;
  StringArrayHelper = record helper(TArrayHelper<string>) for StringArray
  public
    function Join(const Separator: string): string;
  end;
  IntArrayHelper = record helper(TArrayHelper<Integer>) for IntArray
  public
    function Join(const Separator: string): string;
  end;
var
  Values: IntArray;
begin
  Values.Push(1);
  Values.Push(4);
  Values.Push(5);
  WriteLn(Values.Join(' | ')); // outputs '1 | 4 | 5'
end.

Reply by email

Sven Barth
to Anthony Walter
Anthony Walter wrote
I had a request. It would be nice if we could use class helpers with generic types like so:

You are not the first, you won't be the last, but my answer will be the same: nearly impossible to implement.

Regards,
Sven

Reply by email

Anthony Walter
to Sven Barth

Okay, no problems Sven. Thanks again for doing what you've done. I feel like the class helpers on built ins is so great already. By the way, after adding in some I decided to write an FTP client implementation using StringHelper to great effect. Here's how it worked out (and fully tested):

http://www.getlazarus.org/helpers/ftpclient/

When you remove the comments it's probably around only 500 lines and was easy to implement thanks to helper methods like Contains, Between, Split, Pop, Words.

Example from PASV command:

'227 Entering Passive Mode (157,28,148,37,129,44)'

Becomes (at line 430):

Values := S.Between('(', ')').SplitInt(','); // Values now is array of 6
integers

Example from LIST command:

'lrwxrwxrwx 1 0 0 3 Jul 02 2013 public -> pub'

Becomes (at line 637):

Columns := S.Words(9); // Columns is now an array of 9 trimmed strings

It's so easy now!

Reply by email