home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 9 / 09.iso / e / e032 / 3.ddi / FILES / EXAMPLES.PAK / STRINGPA.M < prev   
Encoding:
Text File  |  1992-07-29  |  10.8 KB  |  381 lines

  1. (* :Title: String Patterns *)
  2.  
  3. (* :Context: Examples`StringPatterns` *)
  4.  
  5. (* :Author: John M. Novak *)
  6.  
  7. (* :Summary: Implements a method of more general pattern
  8.     matching with strings than the built-in * and @.
  9. *)
  10.  
  11. (* :Package Version: 2.0 *)
  12.  
  13. (* :Mathematica Version: 2.0 *)
  14.  
  15. (* :History:
  16.     V 2.0, John M. Novak, July 1991 : switch to Mathematica-like
  17.         patterns.
  18.     V 1.0, John M. Novak, June 1991
  19. *)
  20.  
  21. (* :Keywords:
  22.     pattern matching, strings
  23. *)
  24.  
  25. (* :Warning:
  26.     Adds rules to StringMatchQ and StringPosition.
  27. *)
  28.  
  29. (* :Limitation:
  30.     Certain complex patterns take considerably longer to
  31.     evaluate than one might expect...
  32. *)
  33.  
  34. (* :Limitation:
  35.     Many instances of variables of the form $nnn may be left
  36.     behind if any of the special patterns (as described below)
  37.     are used.
  38. *)
  39.  
  40. (* :Discussion:
  41.     This package adds the capability of Mathematica pattern
  42.     matching for strings.  It allows the use of standard Mathematica
  43.     patterns connected by StringJoin to be matched to a string,
  44.     using the functions StringMatchQ and StringPosition.
  45.     The patterns treat a string as a sequence of characters, in
  46.     direct correlation to sequences in normal Mathematica expressions.
  47.     Thus, we can have the pattern:
  48.     "a"<>___<>"b"
  49.     which matches to a string that starts with a, ends with b, and
  50.     has zero or more characters in between.  All standard Mathematica 
  51.     patterns are allowed.
  52.     There are also several enhancements, dealing with matching to
  53.     a sequence of characters, for instance:
  54.     "a"<>("bc"..)<>"d"
  55.     matches any string beginning with a, ending with d, and with
  56.     one or more occurences of the substring "bc" in between.
  57.     Note that this diverges from standard Mathematica patterns, as
  58.     (b,c).. is not normally a valid pattern.  The patterns with
  59.     this enhancement are Repeated, RepeatedNull, and Alternatives.
  60.     
  61.     The implementation is simple enough.  The basic match is
  62.     performed by separating the target string into a list of
  63.     characters, and modifying the pattern to be a list of
  64.     characters and patterns.  The problems come with the
  65.     new pattern cases - replacement rules are used to replace
  66.     the new patterns with valid old patterns that do the
  67.     same thing - unfortunately, the old patterns are often
  68.     rather complex, involving extensive use of Condition[] and
  69.     unique new variables.  This can cause slow evaluation in
  70.     some cases.
  71. *)
  72.  
  73. (* We need to make sure that the usage messages for StringMatchQ
  74.     and StringPosition are loaded before we attempt to modify
  75.     them. *)
  76.  
  77. If[!ValueQ[MessageName[StringMatchQ,"usage"]],
  78.     $NewMessage[StringMatchQ,"usage"]
  79. ];
  80.  
  81. If[!ValueQ[MessageName[StringPosition,"usage"]],
  82.     $NewMessage[StringPosition,"usage"]
  83. ];
  84.  
  85. BeginPackage["Examples`StringPatterns`"]
  86.  
  87. StringMatchQ::usage = MessageName[StringMatchQ,"usage"] <>
  88.     "\nStringMatchQ[string,pattern] determines whether or not
  89.     the string matches the pattern, where the pattern is as
  90.     described in the discussion section of the StringPattern
  91.     package."
  92.  
  93. StringPosition::usage = MessageName[StringPosition,"usage"] <>
  94.     "\nStringPosition[string,pattern] finds occurences of the
  95.     pattern in the string, where the pattern is as described in
  96.     the discussion section of the StringPattern package.  In this
  97.     usage, an additional option, AnchorMatch, is allowed."
  98.  
  99. AnchorMatch::usage =
  100.     "An option for StringPosition, used only with a string
  101.     pattern.  Determines whether a pattern should be anchored
  102.     to the head or tail of the string.  Accepts the values of
  103.     AnchorHead, AnchorTail, or False."
  104.  
  105. AnchorHead::usage =
  106.     "A value for the option AnchorMatch.  Determines that
  107.     the pattern will be anchored to the beginning of the
  108.     string.";
  109.  
  110. AnchorTail::usage =
  111.     "A value for the option AnchorMatch.  Determines that
  112.     the pattern will be anchored to the end of the
  113.     string.";
  114.  
  115. CharacterRange::usage =
  116.     "CharacterRange[char1,char2] generates a pattern for use
  117.     with StringMatchQ and StringPosition.  The pattern matches
  118.     any character between char1 and char2, as determined by
  119.     $StringOrder.  Note that this is not case sensitive."
  120.  
  121. Begin["`Private`"]
  122.  
  123. Unprotect[StringMatchQ,StringPosition];
  124.  
  125. StringMatchQ[str_String,pat_?PatternHeadQ,opts___] :=
  126.     StringMatchQ[str,StringJoin[pat],opts]
  127.  
  128. StringMatchQ[stringi_String,
  129.         StringJoin[pati___],opts___] :=
  130. Module[{ic,string,pat},
  131.     {ic} = {IgnoreCase}/.{opts}/.Options[StringMatchQ];
  132.     If[TrueQ[ic],
  133.         string = ToLowerCase[stringi];
  134.             pat = pati/.s_String :> ToLowerCase[s],
  135.         string = stringi;
  136.         pat = pati
  137.     ];
  138.     MatchQ[Characters[string],
  139.         Map[If[Head[#] === String,
  140.                 Sequence @@ Characters[#],
  141.                 ReleaseHold[#]]&,
  142.             {pat}/.
  143.                 {Literal[Repeated][s_String]:>
  144.                     repstrrule[Unique[],s],
  145.                 Literal[RepeatedNull][s_String]:>
  146.                     nrepstrrule[Unique[],s],
  147.                 Literal[Repeated][Literal[Alternatives][
  148.                         p1___,p2_String?(StringLength[#] > 1&),
  149.                         p3___]] :>
  150.                     repaltstrrule[Unique[],p1,p2,p3],
  151.                 Literal[RepeatedNull][Literal[Alternatives][
  152.                         p1___,p2_String?(StringLength[#] > 1&),
  153.                         p3___]] :>
  154.                     nrepaltstrrule[Unique[],p1,p2,p3],
  155.                 Literal[Alternatives][f___,
  156.                         s_String?(StringLength[#] > 1 &),
  157.                         l___] :>
  158.                     altstrrule[Unique[],{f,s,l}]
  159.                 }
  160.         ]
  161.     ]
  162. ]
  163.  
  164. StringPosition::anchor =
  165.     "Warning: the option AnchorMatch is valid only if using
  166.     a pattern of the sort defined in the StringPatterns
  167.     package!";
  168.  
  169. Options[StringPosition] =
  170.     Append[Options[StringPosition],AnchorMatch->False];
  171.  
  172. StringPosition[str_String,pat_?PatternHeadQ,opts___] :=
  173.     StringPosition[str,StringJoin[pat],opts]
  174.  
  175. StringPosition[istr_String,ipat_StringJoin,opts___] :=
  176.     Module[{ic,ov,am,str,pat},
  177.         {ic,ov,am} = {IgnoreCase,Overlaps,AnchorMatch}/.
  178.             {opts}/.Options[StringPosition];
  179.         If[TrueQ[ic],
  180.             str = ToLowerCase[istr];
  181.                 pat = ipat/.s_String :> ToLowerCase[s],
  182.             str = istr;
  183.                 pat = ipat
  184.         ];
  185.         findsegs[am,TrueQ[ov],str,pat]
  186.     ]
  187.  
  188. StringPosition[arg1___,AnchorMatch -> _,arg2___] :=
  189.     (Message[StringPosition::anchor];
  190.     StringPosition[arg1,arg2])
  191.  
  192. Protect[StringMatchQ,StringPosition];
  193.  
  194. (* utility function that returns True if the Head of the expr
  195.     is a known pattern, False otherwise. *)
  196.  
  197. PatternHeadQ[expr_] :=
  198.     MemberQ[{Repeated,
  199.             RepeatedNull,
  200.             Alternatives,
  201.             Pattern,
  202.             Blank,
  203.             BlankNullSequence,
  204.             BlankSequence,
  205.             Condition,
  206.             PatternTest},
  207.         Head[expr]
  208.     ]
  209.  
  210. (* utility function that returns True if comp is composed of
  211.     str repeated, False otherwise. *)
  212.  
  213. RepeatedStringQ[str_String,comp___] :=
  214.     With[{ln = Length[{comp}]/StringLength[str]},
  215.         StringJoin[Table[str,{ln}]] ===
  216.             StringJoin[comp]/;Head[ln] === Integer
  217.     ]
  218.  
  219. RepeatedStringQ[___] := False
  220.  
  221. (* similar to above, except if comp is empty (Null) then
  222.     the pattern matches as well. *)
  223.  
  224. RepeatedNullStringQ[str_,comp___] :=
  225.     TrueQ[{comp} === {} || RepeatedStringQ[str,comp]]
  226.  
  227. (* Note that RulDelayed::rhs needs to be shut off for a bit -
  228.     Many of the following rules define patterns, which confuses
  229.     SetDelayed, generating a warning.  The warning can be safely
  230.     ignored here, so I just shut it off temporarily. *)
  231.  
  232. Off[RuleDelayed::rhs]
  233.  
  234. (* replacement patterns for new pattern types... *)
  235. (* repeated string *)
  236.  
  237. repstrrule[var_,str_] :=
  238.     Hold[var__/;RepeatedStringQ[str,var]]
  239.  
  240. (* repeatednull string *)
  241.  
  242. nrepstrrule[var_,str_] :=
  243.     Hold[var___/;RepeatedNullStringQ[str,var]]
  244.  
  245. (* repeated with string alternatives *)
  246.  
  247. repaltstrrule[var_,pats___] :=
  248.     Hold[var___/;rpats[{var},pats]]
  249.  
  250. (* repeatednull with string alternatives *)
  251.  
  252. nrepaltstrrule[var_,pats___] :=
  253.     Hold[var___/;apats[{var},pats]]
  254.  
  255. (* string alternatives - splits up into string and pattern
  256.     alternates, then calls alts sub-rule *)
  257.  
  258. altstrrule[var_,args_] :=
  259.     Module[{sargs,oargs},
  260.         sargs = Select[args,(Head[#] === String &&
  261.                 StringLength[#] > 1)&];
  262.         oargs = Complement[args,sargs];
  263.         alts[var,sargs,oargs]
  264.     ]
  265.  
  266. (* sub-rule for alternatives - does the match *)
  267.  
  268. alts[var_,sargs_,{oargs___}] :=
  269.     Alternatives[var___/;MemberQ[sargs,StringJoin[var]],
  270.             oargs]
  271.  
  272. (* sub-rule for nrepaltstrrule, splits up strings and other
  273.     patterns, then maps an individual comparison across each
  274.     piece of the input *)
  275.  
  276. apats[input_,pats___] :=
  277.     Module[{spats,opats},
  278.         spats = Select[{pats},Head[#] === String &];
  279.         opats = Complement[{pats},spats];
  280.         MatchQ[input,Map[
  281.             ReleaseHold[ipat[Unique[],spats,opats]]&,input]]
  282.     ]
  283.  
  284. (* sub-rule for repaltstrrule, much as apats, but uses slightly
  285.     different test for first element. *)
  286.  
  287. rpats[input_,pats___] :=
  288.     Module[{pat,spats,opats},
  289.         spats = Select[{pats},Head[#] === String &];
  290.         opats = Complement[{pats},spats];
  291.         MatchQ[input,
  292.             Prepend[Map[
  293.                 ReleaseHold[ipat[Unique[],spats,opats]]&,input],
  294.                 ReleaseHold[inpat[Unique[],spats,opats]]]
  295.         ]
  296.     ]
  297.  
  298. (* sub-rule for apats, does individual comparison *)
  299.  
  300. ipat[var_,spats_,opats_] :=
  301.     Hold[var___/;Or[MemberQ[Append[spats,""],StringJoin[var]],
  302.         MatchQ[{var},{Alternatives @@ opats}]]]
  303.  
  304. inpat[var_,spats_,opats_] :=
  305.     Hold[var___/;Or[MemberQ[spats,StringJoin[var]],
  306.         MatchQ[{var},{Alternatives @@ opats}]]]
  307.  
  308. (* Pattern for range of characters *)
  309.  
  310. CharacterRange[c1_String,c2_String] :=
  311.     _?(OrderedQ[{c1,#,c2}]&)
  312.  
  313. On[RuleDelayed::rhs]
  314.  
  315. (* Handles the various combinations of an anchored match and
  316.     overlaps for StringPosition. Usually does something like
  317.     generating a table of substring positions, then going
  318.     through and extracting them and comparing each to the
  319.     pattern. Returns the list of sub-string positions where
  320.     the match is good. (within the constraints of the options.) *)
  321.  
  322. findsegs[AnchorHead,True,str_,pat_] :=
  323.     Module[{n,m,ln = StringLength[str]},
  324.         Select[Table[{1,n},{n,ln}],
  325.             StringMatchQ[StringTake[str,#],pat]&
  326.         ]
  327.     ]
  328.  
  329. findsegs[AnchorHead,False,str_,pat_] :=
  330.     Module[{n,m,segs,ln = StringLength[str]},
  331.         segs = Table[{1,n},{n,ln}];
  332.         n = 1;
  333.         While[!StringMatchQ[StringTake[str,segs[[n]]],
  334.                 pat] && n <= Length[segs],
  335.             n += 1];
  336.         If[n <= Length[segs],{segs[[n]]},{}]
  337.     ]
  338.  
  339. findsegs[AnchorTail,True,str_,pat_] :=
  340.     Module[{n,m,ln = StringLength[str]},
  341.         Select[Table[{n,ln},{n,ln,1,-1}],
  342.             StringMatchQ[StringTake[str,#],pat]&]
  343.     ]
  344.  
  345. findsegs[AnchorTail,False,str_,pat_] :=
  346.     Module[{n,m,ln = StringLength[str],segs},
  347.         segs = Table[{n,ln},{n,ln,1,-1}];
  348.         n = 1;
  349.         While[!StringMatchQ[StringTake[str,segs[[n]] ],
  350.                 pat] && n <= Length[segs],
  351.             n += 1];
  352.         If[n <= Length[segs],{segs[[n]]},{}]
  353.     ]
  354.  
  355. findsegs[_,True,str_,pat_] :=
  356.     Module[{n,m,ln = StringLength[str]},
  357.         Select[Flatten[Table[{n,m},{n,ln},{m,n,ln}],1],
  358.             StringMatchQ[StringTake[str,#],pat]&]
  359.     ]
  360.  
  361. findsegs[_,False,str_,pat_] :=
  362.     Module[{n,m,ln = StringLength[str],out = {}},
  363.         m = 1;
  364.         While[m <= ln,
  365.             n = m;
  366.             While[n <= ln && !StringMatchQ[
  367.                     StringTake[str,{m,n}],pat],
  368.                 n += 1];
  369.             If[n <= ln,
  370.                 AppendTo[out,{m,n}];m = n,
  371.                 m += 1]
  372.         ];
  373.         out
  374.     ]
  375.  
  376. findsegs[___] := {}
  377.  
  378. End[]
  379.  
  380. EndPackage[]
  381.