Instr for Pascal

From ISXKB

Jump to: navigation, search

Contents

Instr for Pascal

by Kevin Provance

Argument

Longtime developers who favour Visual Basic should be intimately familiar with the Instr function. It's a build in VB function that returns a Long (Integer in Pascal) value specifying the position of the first occurrence of one string within another.

Wait, don't say it, I can already hear you now, "But Kev, Pascal has the Pos function that does the same thing! So?"

Yes, I am aware of this. If your goal is to always start at the beginning of the string, then Pos should serve you well. But suppose you want to search the string at a point someplace other than the beginning, like VB developers can do with Instr? Is that an, "um, erm...uh..." I am hearing? Pos cannot do that, can it?

The VB Instr function has four parameters: StartPosition, SourceString, SeachString, CompareOp. Pascal's Pos, does not. Instr via it's first parameter allows you to set the starting position to search within the string, and CompareOp allows you to search with or within case sensitivity (Binary vs. Text). Instr then becomes a much more powerful tool for string manipulation.

I was working on a different project for Inno (which will end up here sooner or later) some of which involved translation from C++ and VB code. Imagine my surprise when I discovered there was no Instr equivalent that would let me choose the starting index, or search backward through a string (VBers will know this as InstrRev). I was now left with the ultimate and most dreaded distraction: Writing my own Instr and InstrRev. A pain in the ass for me, but beneficial for everyone else as I have yet to find an Instr equivalent for Pascal. Now one exists, and now I share it with you.

Prerequisites

None.

Code

As I always recommend - especially if you are into reusable code, as I am - placing this code into it's own ISS file and importing it into your project. This way you can call to it from any project without cut and pasting it over and over again. It's also the way I am presenting this code, as a stand alone script. Please, take it and do with it as you will. If you happen to edit it for better performance (it is kind of slow, I admit it), all I ask is you share your modification here on this page. I won't mind if you do. If fact, I encourage it.

[Code]

// InStrEx
// Replacement function for Pos().  It includes both the VB Instr and InstrRev functions.

// Synopsis:
// InStrEx(StartPos: Integer; 
//         SourceString: String;
//         SearchString: String;
//         SearchMode:   Integer;
//         OrdSelector:  Integer): Integer
//
//         StartPos    : The character position from where the search
//                       begins (1 = first character of SourceString, etc.)
//                       Zero (0) would signify the search starts at the beginning of the string.
//
//         SourceString: String expression being searched.
//
//         SearchString: String expression sought.
//
//         SearchMode  : Range of flags specified by the following constants.
//
//                       isForward : Specifies forward search through the SourceString. 
//
//                       isBackward: Specifies backward search through the SourceString.
//
//                       isNumber  : Instructs the function to find the NUMBER of occurrences
//                                   of SearchString within SourceString, starting from StartPos,
//                                   and searching in the relevant direction.
//
//                       isNoCase  : if added to any combination of the above, specifies the 
//                                   search be performed as case INSENSITIVE. Case sensitive 
//                                   is the default.
//
//                       These constants are added together bitwise using 'OR' to combine 
//                       modes (e.g., isBackward or isNumber will search backwards from
//                       StartPos, and count how many occurrences of SearchString it has
//                       found in SourceString.  If no further occurrences of SearchString
//                       are found from the chosen search point, then InStrEx() returns zero.
//
//         OrdSelector : Specifies which occurrence of SearchString is to be searched for.  
//                       When not used, it should be set to zero.  NOTE: This flag is not
//                       valid when used in conjunction with isNumber.)

const
   // InstrEx mode flags
   isForward  = 0;
   isBackward = 1;
   isNumber   = 2;
   isNoCase   = 4;

function InStrEx(StartPos: Integer; SourceString: String; 
                 SearchString: String; SearchMode:Integer; 
                 OrdSelector: Integer):Integer;

var
z:           Integer;
cn:          Integer;
ChrStepping: Integer;
ChrFrom:     Integer;
ChrTo:       Integer;
tmpStr1:     String;
tmpStr2:     String;
Str1:        String;

begin
   If SourceString <>  then begin
      
      // Create temp copies of our source and search strings.
      tmpStr1 := SourceString
      tmpStr2 := SearchString

      Result := 0

      // Just in case the dev (you know, you) forgets to specify either isForward
      // or isBackward when specifying other flags.
      If (SearchMode and isBackward) = 0 then begin
         SearchMode := SearchMode or isForward;
      end; 

      // This conditional test checks to see if bit 2 of IS_Mode (corresponding to
      // a value of 4) is set. If it is, then we've selected no case sensitivity.

      // To make a case insensitive search, make our temp strings all uppercase.
      If (SearchMode and isNoCase) <> 0 then begin
         tmpStr1 := UpperCase(tmpStr1);
         tmpStr2 := UpperCase(tmpStr2);			
      end;

      // This is a check to see if user has entered a nonsensical value for the
      // ordinal selector. If so, set it to a default value of 1 initially,
      // then correct the value because, for the first search, it actually needs
      // to be zero (and so on for the other values ... N-1)
      cn := OrdSelector;
      If cn = 0 then begin
         cn := 1;
      end;

      cn := cn - 1

      // This select checks the lowest bit position only (i.e., bit 0). If it's set,
      // then we're searching backwards, otherwise we're searching forwards.
      case (SearchMode and $00000001) of
         isForward:
            begin
               If StartPos = 0 then begin
                  StartPos := 1;
               end;
               ChrFrom := StartPos;
               ChrTo := Length(tmpStr1);

               // Now actually start searching for our string!
               // This For/Loop counts up, as we are searching forward in our string.
               For z := ChrFrom to ChrTo do begin

                  // Grabs the character(s) of the string based on it's position in the loop.
                  Str1 := Copy(tmpStr1, z, Length(tmpStr2));
                  
                  // Compare those characters to the temp search string.
                  If Str1 = tmpStr2 then begin

                     // If checking for instance count of the search string, add it!
                     // If not, return the count of the loop, which is the position of the first
                     // occurrence from the StartPos.
                     If (SearchMode and isNumber) <> 0 then begin
                        Result := Result + 1;
                     end else begin
                        Result := z;

                        // If we are not dealing with the ordinal option, exit the function.
                        // Otherwise add one to the ordinal count.
                        If cn = 0 then begin
                           exit;
                        end else begin
                           cn := cn - 1;
                        end;
                     end;
                  end;
               end;				
            end;

         // The same comments above apply below for isBackwards, except where noted.
         isBackward:
            begin
               If StartPos = 0 then begin
                  StartPos := Length(tmpStr1);
               end;

               ChrFrom := StartPos;
               ChrTo := 1;

               // This For/Loop counts down, as we are searching backward in our string.
               For z := ChrFrom downto ChrTo do begin
                  Str1 := Copy(tmpStr1, z, Length(tmpStr2));

                  If Str1 = tmpStr2 then begin
                     If (SearchMode and isNumber) <> 0 then begin
                        Result := Result + 1;
                     end else begin
        
                        // Since Pascal does not have the Step keyword in For loops, and since
                        // the loop count counts down from the length of the SourceString, we must
                        // compensate for the accurate number of chars counting backwards.  We do
                        // this by subtracting the loop count from the total number of characters
                        // of the SourceString, plus the length of the SearchString characters.
                        Result := (ChrFrom - z) + Length(tmpStr2);

                        If cn = 0 then begin
                           exit;
                        end else begin
                           cn := cn - 1;
                        end;
                     end;
                  end;
               end;				
            end;
      end;

      // If we're simply performing a forward or backward search, and we reach
      // the end of the loop, then we've scanned the entire SourceString, and not
      // found the specified occurrence of the SearchString. So, we need to signal
      // this fact by returning the special value -1.
      If (SearchMode and isNumber) = 0 then begin
         Result := -1;
      end;
   end;
end;

Usage

Using the InstrEx function is sheer simplicity. When using multiple flags for the SearchMode parameter, should you forget to include all necessary flags, the default search direction is forward and the default search is case sensitive. The default value for SearchPos is zero. When using isForward, zero starts at the beginning of the string; when using isBackward, zero starts the search at the end of the string. When not using the Ordinal parameter, set it to zero.

// Variable to hold the result of InstrEx
var
   lMarker: Integer;

// Sample source message
const
   sSource = 'how now, brown cow.';

// Forward search for 'now'. (5)
lMarker := InstrEx(0, sSource, 'now', isForward, 0);

// Backward search for the comma. (12)
lMarker := InstrEx(0, sSource, ',', isBackward, 0);

// Number of instances of the letter 'o'. (4)
lMarker := InstrEx(0, sSource, 'o', isForward or isNumber, 0);

// Position of the third instance of the letter 'o'. (12)
lMarker := InstrEx(0, sSource, 'o', isForward, 3);

// Case insensitive search of the word 'cow' starting at the comma. (16)
lMarker := InstrEx(8, sSource, 'CoW', isForward or isNoCase, 0);
Personal tools
Ads: