home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / OL.LZH / PROCS.LZH / FINDRE.ICN < prev    next >
Text File  |  1991-09-05  |  21KB  |  707 lines

  1. ########################################################################
  2. #    
  3. #    Name:    findre.icn
  4. #    
  5. #    Title:    "Find" Regular Expression
  6. #    
  7. #    Author:    Richard L. Goerwitz
  8. #
  9. #    Version: 1.14
  10. #
  11. #    Date:     June 1, 1991
  12. #
  13. ########################################################################
  14. #
  15. #    findre() is like the Icon builtin function find(),
  16. #  except that it takes, as its first argument, a regular expression
  17. #  pretty much like the ones the Unix egrep command uses (the few
  18. #  minor differences are listed below).  Its syntax is the same as
  19. #  find's (i.e. findre(s1,s2,i,j)), with the exception that a no-
  20. #  argument invocation wipes out all static structures utilized by
  21. #  findre, and then forces a garbage collection.
  22. #
  23. #  (For those not familiar with regular expressions and the Unix egrep
  24. #  command: findre() offers a simple and compact wildcard-based search
  25. #  system.  If you do a lot of searches through text files, or write
  26. #  programs which do searches based on user input, then findre is a
  27. #  utility you might want to look over.)
  28. #
  29. #  Important differences between find and findre:  As noted above,
  30. #  findre() is just a find() function that takes a regular expression
  31. #  as its first argument.  One major problem with this setup is that
  32. #  it leaves the user with no easy way to tab past a matched
  33. #  substring, as with
  34. #    s ? write(tab(find("hello")+5))
  35. #
  36. #  In order to remedy this intrinsic deficiency, findre() sets the
  37. #  global variable __endpoint to the first position after any given
  38. #  match occurs.  Use this variable with great care, preferably
  39. #  assigning its value to some other variable immediately after the
  40. #  match (for example, findre("hello [.?!]*",s) & tmp := __endpoint).
  41. #  Otherwise, you will certainly run into trouble.  (See the example
  42. #  below for an illustration of how __endpoint is used).
  43. #
  44. #  Important differences between egrep and findre:  findre utilizes
  45. #  the same basic language as egrep.  The only big difference is that
  46. #  findre uses intrinsic Icon data structures and escaping conven-
  47. #  tions rather than those of any particular Unix variant.  Be care-
  48. #  ful!  If you put findre("\(hello\)",s) into your source file,
  49. #  findre will treat it just like findre("(hello)",s).  If, however,
  50. #  you enter '\(hello\)' at run-time (via, say, findre(!&input,s)),
  51. #  what Icon receives will depend on your operating system (most
  52. #  likely, a trace will show "\\(hello\\)").
  53. #
  54. #  Bugs:  Space has essentially been conserved at the expense of time
  55. #  in the automata produced by findre().  The algorithm, in other
  56. #  words, will produce the equivalent of a pushdown automaton under
  57. #  certain circumstances, rather than strive (at the expense of space)
  58. #  for full determinism.  I tried to make up a nfa -> dfa converter
  59. #  that would only create that portion of the dfa it needed to accept
  60. #  or reject a string, but the resulting automaton was actually quite
  61. #  slow (if anyone can think of a way to do this in Icon, and keep it
  62. #  small and fast, please let us all know about it).  Note that under
  63. #  version 8 of Icon, findre takes up negligible storage space, due to
  64. #  the much improved hashing algorithm.  I have not tested it under
  65. #  version 7, but I would expect it to use up quite a bit more space
  66. #  in that environment.
  67. #
  68. #  Important note:  findre takes a shortest-possible-match approach
  69. #  to regular expressions.  In other words, if you look for "a*",
  70. #  findre will not even bother looking for an "a."  It will just match
  71. #  the empty string.  Without this feature, findre would perform a bit
  72. #  more slowly.  The problem with such an approach is that often the
  73. #  user will want to tab past the longest possible string of matched
  74. #  characters (say tab((findre("a*|b*"), __endpoint)).  In circumstan-
  75. #  ces like this, please just use something like:
  76. #
  77. #      s ? {
  78. #          tab(find("a")) &  # or use Arb() from the IPL (patterns.icn)
  79. #          tab(many('a'))
  80. #          tab(many('b'))
  81. #      }
  82. #
  83. #  or else use some combination of findre and the above.
  84. #    
  85. ########################################################################
  86. #
  87. #  Regular expression syntax: Regular expression syntax is complex,
  88. #  and yet simple.  It is simple in the sense that most of its power
  89. #  is concentrated in about a dozen easy-to-learn symbols.  It is
  90. #  complex in the sense that, by combining these symbols with
  91. #  characters, you can represent very intricate patterns.
  92. #
  93. #  I make no pretense here of offering a full explanation of regular
  94. #  expressions, their usage, and the deeper nuances of their syntax.
  95. #  As noted above, this should be gleaned from a Unix manual.  For
  96. #  quick reference, however, I have included a brief summary of all
  97. #  the special symbols used, accompanied by an explanation of what
  98. #  they mean, and, in some cases, of how they are used (most of this
  99. #  is taken from the comments prepended to Jerry Nowlin's Icon-grep
  100. #  command, as posted a couple of years ago):
  101. #
  102. #     ^   -  matches if the following pattern is at the beginning
  103. #            of a line (i.e. ^# matches lines beginning with "#")
  104. #     $   -  matches if the preceding pattern is at the end of a line
  105. #     .   -  matches any single character
  106. #     +   -  matches from 1 to any number of occurrences of the
  107. #            previous expression (i.e. a character, or set of paren-
  108. #            thesized/bracketed characters)
  109. #     *   -  matches from 0 to any number of occurrences of the previous
  110. #            expression
  111. #     \   -  removes the special meaning of any special characters
  112. #            recognized by this program (i.e if you want to match lines
  113. #            beginning with a "[", write ^\[, and not ^[)
  114. #     |   -  matches either the pattern before it, or the one after
  115. #            it (i.e. abc|cde matches either abc or cde)
  116. #     []  -  matches any member of the enclosed character set, or,
  117. #            if ^ is the first character, any nonmember of the
  118. #            enclosed character set (i.e. [^ab] matches any character
  119. #         _except_ a and b).
  120. #     ()  -  used for grouping (e.g. ^(abc|cde)$ matches lines consist-
  121. #            ing of either "abc" or "cde," while ^abc|cde$ matches
  122. #            lines either beginning with "abc" or ending in "cde")
  123. #
  124. #########################################################################
  125. #
  126. #  Example program:
  127. #
  128. #  procedure main(a)
  129. #      while line := !&input do {
  130. #          token_list := tokenize_line(line,a[1])
  131. #          every write(!token_list)
  132. #      }
  133. #  end
  134. #
  135. #  procedure tokenize_line(s,sep)
  136. #      tmp_lst := []
  137. #      s ? {
  138. #          while field := tab(findre(sep)|0) &
  139. #          mark := __endpoint
  140. #          do {
  141. #              put(tmp_lst,"" ~== field)
  142. #              if pos(0) then break
  143. #              else tab(mark)
  144. #          }
  145. #      }
  146. #      return tmp_lst
  147. #  end
  148. #
  149. #  The above program would be compiled with findre (e.g. "icont
  150. #  test_prg.icn findre.icn") to produce a single executable which
  151. #  tokenizes each line of input based on a user-specified delimiter.
  152. #  Note how __endpoint is set soon after findre() succeeds.  Note
  153. #  also how empty fields are excluded with "" ~==, etc.  Finally, note
  154. #  that the temporary list, tmp_lst, is not needed.  It is included
  155. #  here merely to illustrate one way in which tokens might be stored.
  156. #
  157. #  Tokenizing is, of course, only one of many uses one might put
  158. #  findre to.  It is very helpful in allowing the user to construct
  159. #  automata at run-time.  If, say, you want to write a program that
  160. #  searches text files for patterns given by the user, findre would be
  161. #  a perfect utility to use.  Findre in general permits more compact
  162. #  expression of patterns than one can obtain using intrinsic Icon
  163. #  scanning facilities.  Its near complete compatibility with the Unix
  164. #  regexp library, moreover, makes for greater ease of porting,
  165. #  especially in cases where Icon is being used to prototype C code.
  166. #
  167. #########################################################################
  168.  
  169. global state_table, parends_present, slash_present
  170. global biggest_nonmeta_str, __endpoint
  171. record o_a_s(op,arg,state)
  172.  
  173.  
  174. procedure findre(re, s, i, j)
  175.  
  176.     local p, x, nonmeta_len
  177.     static FSTN_table, STRING_table
  178.     initial {
  179.     FSTN_table := table()
  180.     STRING_table := table()
  181.     }
  182.  
  183.     if /re then {
  184.     FSTN_table := table()
  185.     STRING_table := table()
  186.     collect()  # do it *now*
  187.     return
  188.     }
  189.  
  190.     /s := &subject
  191.     if \i then {
  192.     if i < 1 then
  193.         i := *s + (i+1)
  194.     }
  195.     else i := \&pos | 1
  196.     if \j then {
  197.     if j < 1 then
  198.         j := *s + (j+1)
  199.     }
  200.  
  201.     else j := *s+1
  202.     if /FSTN_table[re] then {
  203.     # If we haven't seen this re before, then...
  204.     if \STRING_table[re] then {
  205.         # ...if it's in the STRING_table, use plain find()
  206.         every p := find(STRING_table[re],s,i,j)
  207.         do { __endpoint := p + *STRING_table[re]; suspend p }
  208.         fail
  209.     }
  210.     else {
  211.         # However, if it's not in the string table, we have to
  212.         # tokenize it and check for metacharacters.  If it has
  213.         # metas, we create an FSTN, and put that into FSTN_table;
  214.         # otherwise, we just put it into the STRING_table.
  215.         tokenized_re := tokenize(re)
  216.         if 0 > !tokenized_re then {
  217.         # if at least one element is < 0, re has metas
  218.         MakeFSTN(tokenized_re) | err_out(re,2)
  219.         # both biggest_nonmeta_str and state_table are global
  220.         /FSTN_table[re] := [.biggest_nonmeta_str, copy(state_table)]
  221.         }
  222.         else {
  223.         # re has no metas; put the input string into STRING_table
  224.         # for future reference, and execute find() at once
  225.         tmp := ""; every tmp ||:= char(!tokenized_re)
  226.         insert(STRING_table,re,tmp)
  227.         every p := find(STRING_table[re],s,i,j)
  228.         do { __endpoint := p + *STRING_table[re]; suspend p }
  229.         fail
  230.         }
  231.     }
  232.     }
  233.  
  234.  
  235.     if nonmeta_len := (1 < *FSTN_table[re][1]) then {
  236.     # If the biggest non-meta string in the original re
  237.     # was more than 1, then put in a check for it...
  238.     s[1:j] ? {
  239.         tab(x := i to j - nonmeta_len) &
  240.         (find(FSTN_table[re][1]) | fail) \ 1 &
  241.         (__endpoint := apply_FSTN(&null,FSTN_table[re][2])) &
  242.         (suspend x)
  243.     }
  244.     }
  245.     else {
  246.     #...otherwise it's not worth worrying about the biggest nonmeta str
  247.     s[1:j] ? {
  248.         tab(x := i to j) &
  249.         (__endpoint := apply_FSTN(&null,FSTN_table[re][2])) &
  250.         (suspend x)
  251.     }
  252.     }
  253.  
  254. end
  255.  
  256.  
  257.  
  258. procedure apply_FSTN(ini,tbl)
  259.  
  260.     static s_tbl
  261.     local POS, tmp, fin
  262.  
  263.     /ini := 1 & s_tbl := tbl & biggest_pos := 1
  264.     if ini = 0 then {
  265.     return &pos
  266.     }
  267.     POS := &pos
  268.     fin := 0
  269.  
  270.     repeat {
  271.     if tmp := !s_tbl[ini] &
  272.         tab(tmp.op(tmp.arg))
  273.     then {
  274.         if tmp.state = fin
  275.         then return &pos
  276.         else ini := tmp.state
  277.     }
  278.     else (&pos := POS, fail)
  279.     }
  280.  
  281. end
  282.     
  283.  
  284.  
  285. procedure tokenize(s)
  286.  
  287.     local chr, tmp
  288.  
  289.     token_list := list()
  290.     s ? {
  291.     tab(many('*+?|'))
  292.     while chr := move(1) do {
  293.         if chr == "\\"
  294.         # it can't be a metacharacter; remove the \ and "put"
  295.         # the integer value of the next chr into token_list
  296.         then put(token_list,ord(move(1))) | err_out(s,2,chr)
  297.         else if any('*+()|?.$^',chr)
  298.         then {
  299.         # Yuck!  Egrep compatibility stuff.
  300.         case chr of {
  301.             "*"    : {
  302.             tab(many('*+?'))
  303.             put(token_list,-ord("*"))
  304.             }
  305.             "+"    : {
  306.             tmp := tab(many('*?+')) | &null
  307.             if upto('*?',\tmp)
  308.             then put(token_list,-ord("*"))
  309.             else put(token_list,-ord("+"))
  310.             }
  311.             "?"    : {
  312.             tmp := tab(many('*?+')) | &null
  313.             if upto('*+',\tmp)
  314.             then put(token_list,-ord("*"))
  315.             else put(token_list,-ord("?"))
  316.             }
  317.             "("    : {
  318.             tab(many('*+?'))
  319.             put(token_list,-ord("("))
  320.             }
  321.             default: {
  322.             put(token_list,-ord(chr))
  323.             }
  324.         }
  325.         }
  326.         else {
  327.         case chr of {
  328.             # More egrep compatibility stuff.
  329.             "["    : {
  330.             b_loc := find("[") | *&subject+1
  331.             every next_one := find("]",,,b_loc)
  332.             \next_one ~= &pos | err_out(s,2,chr)
  333.             put(token_list,-ord(chr))
  334.             }
  335.                     "]"    : {
  336.             if &pos = (\next_one+1)
  337.             then put(token_list,-ord(chr)) &
  338.                  next_one := &null
  339.             else put(token_list,ord(chr))
  340.             }
  341.             default: put(token_list,ord(chr))
  342.         }
  343.         }
  344.     }
  345.     }
  346.  
  347.     token_list := UnMetaBrackets(token_list)
  348.  
  349.     fixed_length_token_list := list(*token_list)
  350.     every i := 1 to *token_list
  351.     do fixed_length_token_list[i] := token_list[i]
  352.     return fixed_length_token_list
  353.  
  354. end
  355.  
  356.  
  357.  
  358. procedure UnMetaBrackets(l)
  359.  
  360.     # Since brackets delineate a cset, it doesn't make
  361.     # any sense to have metacharacters inside of them.
  362.     # UnMetaBrackets makes sure there are no metacharac-
  363.     # ters inside of the braces.
  364.  
  365.     local tmplst, i, Lb, Rb
  366.  
  367.     tmplst := list(); i := 0
  368.     Lb := -ord("[")
  369.     Rb := -ord("]")
  370.  
  371.     while (i +:= 1) <= *l do {
  372.     if l[i] = Lb then {
  373.         put(tmplst,l[i])
  374.         until l[i +:= 1] = Rb
  375.         do put(tmplst,abs(l[i]))
  376.         put(tmplst,l[i])
  377.     }
  378.     else put(tmplst,l[i])
  379.     }
  380.     return tmplst
  381.  
  382. end
  383.  
  384.  
  385.  
  386. procedure MakeFSTN(l,INI,FIN)
  387.  
  388.     # MakeFSTN recursively descends through the tree structure
  389.     # implied by the tokenized string, l, recording in (global)
  390.     # fstn_table a list of operations to be performed, and the
  391.     # initial and final states which apply to them.
  392.  
  393.     # global biggest_nonmeta_str, slash_present, parends_present
  394.     static Lp, Rp, Sl, Lb, Rb, Caret_inside, Dot, Dollar, Caret_outside
  395.     local i, inter, inter2, tmp
  396.     initial {
  397.     Lp := -ord("("); Rp := -ord(")")
  398.     Sl := -ord("|")
  399.     Lb := -ord("["); Rb := -ord("]"); Caret_inside := ord("^")
  400.     Dot := -ord("."); Dollar := -ord("$"); Caret_outside := -ord("^")
  401.     }
  402.  
  403.     /INI := 1 & state_table := table() &
  404.     NextState("new") & biggest_nonmeta_str := ""
  405.     /FIN := 0
  406.  
  407.     # I haven't bothered to test for empty lists everywhere.
  408.     if *l = 0 then {
  409.     /state_table[INI] := []
  410.     put(state_table[INI],o_a_s(zSucceed,&null,FIN))
  411.     return
  412.     }
  413.  
  414.     # HUNT DOWN THE SLASH (ALTERNATION OPERATOR)
  415.     every i := 1 to *l do {
  416.     if l[i] = Sl & tab_bal(l,Lp,Rp) = i then {
  417.         if i = 1 then err_out(l,2,char(abs(l[i]))) else {
  418.         /slash_present := "yes"
  419.         inter := NextState()
  420.         inter2:= NextState()
  421.         MakeFSTN(l[1:i],inter2,FIN)
  422.         MakeFSTN(l[i+1:0],inter,FIN)
  423.         /state_table[INI] := []
  424.         put(state_table[INI],o_a_s(apply_FSTN,inter2,0))
  425.         put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  426.         return
  427.         }
  428.     }
  429.     }
  430.  
  431.     # HUNT DOWN PARENTHESES
  432.     if l[1] = Lp then {
  433.     i := tab_bal(l,Lp,Rp) | err_out(l,2,"(")
  434.     inter := NextState()
  435.     if any('*+?',char(abs(0 > l[i+1]))) then {
  436.         case l[i+1] of {
  437.         -ord("*")   : {
  438.             /state_table[INI] := []
  439.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  440.             MakeFSTN(l[2:i],INI,INI)
  441.             MakeFSTN(l[i+2:0],inter,FIN)
  442.             return
  443.         }
  444.         -ord("+")   : {
  445.             inter2 := NextState()
  446.             /state_table[inter2] := []
  447.             MakeFSTN(l[2:i],INI,inter2)
  448.             put(state_table[inter2],o_a_s(apply_FSTN,inter,0))
  449.             MakeFSTN(l[2:i],inter2,inter2)
  450.             MakeFSTN(l[i+2:0],inter,FIN)
  451.             return
  452.         }
  453.         -ord("?")   : {
  454.             /state_table[INI] := []
  455.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  456.             MakeFSTN(l[2:i],INI,inter)
  457.             MakeFSTN(l[i+2:0],inter,FIN)
  458.             return
  459.         }
  460.         }
  461.     }
  462.     else {
  463.         MakeFSTN(l[2:i],INI,inter)
  464.         MakeFSTN(l[i+1:0],inter,FIN)
  465.         return
  466.     }
  467.     }
  468.     else {     # I.E. l[1] NOT = Lp (left parenthesis as -ord("("))
  469.     every i := 1 to *l do {
  470.         case l[i] of {
  471.         Lp     : {
  472.             inter := NextState()
  473.             MakeFSTN(l[1:i],INI,inter)
  474.             /parends_present := "yes"
  475.             MakeFSTN(l[i:0],inter,FIN)
  476.             return
  477.         }
  478.         Rp     : err_out(l,2,")")
  479.         }
  480.     }
  481.     }
  482.  
  483.     # NOW, HUNT DOWN BRACKETS
  484.     if l[1] = Lb then {
  485.     i := tab_bal(l,Lb,Rb) | err_out(l,2,"[")
  486.     inter := NextState()
  487.     tmp := ""; every tmp ||:= char(l[2 to i-1])
  488.     if Caret_inside = l[2]
  489.     then tmp := ~cset(Expand(tmp[2:0]))
  490.     else tmp :=  cset(Expand(tmp))
  491.     if any('*+?',char(abs(0 > l[i+1]))) then {
  492.         case l[i+1] of {
  493.         -ord("*")   : {
  494.             /state_table[INI] := []
  495.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  496.             put(state_table[INI],o_a_s(any,tmp,INI))
  497.             MakeFSTN(l[i+2:0],inter,FIN)
  498.             return
  499.         }
  500.         -ord("+")   : {
  501.             inter2 := NextState()
  502.             /state_table[INI] := []
  503.             put(state_table[INI],o_a_s(any,tmp,inter2))
  504.             /state_table[inter2] := []
  505.             put(state_table[inter2],o_a_s(apply_FSTN,inter,0))
  506.             put(state_table[inter2],o_a_s(any,tmp,inter2))
  507.             MakeFSTN(l[i+2:0],inter,FIN)
  508.             return
  509.         }
  510.         -ord("?")   : {
  511.             /state_table[INI] := []
  512.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  513.             put(state_table[INI],o_a_s(any,tmp,inter))
  514.             MakeFSTN(l[i+2:0],inter,FIN)
  515.             return
  516.         }
  517.         }
  518.     }
  519.     else {
  520.         /state_table[INI] := []
  521.         put(state_table[INI],o_a_s(any,tmp,inter))
  522.         MakeFSTN(l[i+1:0],inter,FIN)
  523.         return
  524.     }
  525.     }
  526.     else {           # I.E. l[1] not = Lb
  527.     every i := 1 to *l do {
  528.         case l[i] of {
  529.         Lb     : {
  530.             inter := NextState()
  531.             MakeFSTN(l[1:i],INI,inter)
  532.             MakeFSTN(l[i:0],inter,FIN)
  533.             return
  534.         }
  535.         Rb     : err_out(l,2,"]")
  536.         }
  537.     }
  538.     }
  539.  
  540.     # FIND INITIAL SEQUENCES OF POSITIVE INTEGERS, CONCATENATE THEM
  541.     if i := match_positive_ints(l) then {
  542.     inter := NextState()
  543.     tmp := Ints2String(l[1:i])
  544.     # if a slash has been encountered already, forget optimizing
  545.         # in this way; if parends are present, too, then forget it,
  546.         # unless we are at the beginning or end of the input string
  547.     if  INI = 1 | FIN = 2 | /parends_present &
  548.         /slash_present & *tmp > *biggest_nonmeta_str
  549.     then biggest_nonmeta_str := tmp
  550.     /state_table[INI] := []
  551.     put(state_table[INI],o_a_s(match,tmp,inter))
  552.     MakeFSTN(l[i:0],inter,FIN)
  553.     return
  554.     }
  555.  
  556.     # OKAY, CLEAN UP ALL THE JUNK THAT'S LEFT
  557.     i := 0
  558.     while (i +:= 1) <= *l do {
  559.     case l[i] of {
  560.         Dot          : { Op := any;   Arg := &cset }
  561.         Dollar       : { Op := pos;   Arg := 0     }
  562.         Caret_outside: { Op := pos;   Arg := 1     }
  563.         default      : { Op := match; Arg := char(0 < l[i]) }
  564.     } | err_out(l,2,char(abs(l[i])))
  565.     inter := NextState()
  566.     if any('*+?',char(abs(0 > l[i+1]))) then {
  567.         case l[i+1] of {
  568.         -ord("*")   : {
  569.             /state_table[INI] := []
  570.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  571.             put(state_table[INI],o_a_s(Op,Arg,INI))
  572.             MakeFSTN(l[i+2:0],inter,FIN)
  573.             return
  574.         }
  575.         -ord("+")   : {
  576.             inter2 := NextState()
  577.             /state_table[INI] := []
  578.             put(state_table[INI],o_a_s(Op,Arg,inter2))
  579.             /state_table[inter2] := []
  580.             put(state_table[inter2],o_a_s(apply_FSTN,inter,0))
  581.             put(state_table[inter2],o_a_s(Op,Arg,inter2))
  582.             MakeFSTN(l[i+2:0],inter,FIN)
  583.             return
  584.         }
  585.         -ord("?")   : {
  586.             /state_table[INI] := []
  587.             put(state_table[INI],o_a_s(apply_FSTN,inter,0))
  588.             put(state_table[INI],o_a_s(Op,Arg,inter))
  589.             MakeFSTN(l[i+2:0],inter,FIN)
  590.             return
  591.         }
  592.         }
  593.     }
  594.     else {
  595.         /state_table[INI] := []
  596.         put(state_table[INI],o_a_s(Op,Arg,inter))
  597.         MakeFSTN(l[i+1:0],inter,FIN)
  598.         return
  599.     }
  600.     }
  601.  
  602.     # WE SHOULD NOW BE DONE INSERTING EVERYTHING INTO state_table
  603.     # IF WE GET TO HERE, WE'VE PARSED INCORRECTLY!
  604.     err_out(l,4)
  605.  
  606. end
  607.  
  608.  
  609.  
  610. procedure NextState(new)
  611.     static nextstate
  612.     if \new then nextstate := 1
  613.     else nextstate +:= 1
  614.     return nextstate
  615. end
  616.  
  617.  
  618.  
  619. procedure err_out(x,i,elem)
  620.     writes(&errout,"Error number ",i," parsing ",image(x)," at ")
  621.     if \elem 
  622.     then write(&errout,image(elem),".")
  623.     else write(&errout,"(?).")
  624.     exit(i)
  625. end
  626.  
  627.  
  628.  
  629. procedure zSucceed()
  630.     return .&pos
  631. end
  632.  
  633.  
  634.  
  635. procedure Expand(s)
  636.  
  637.     s2 := ""
  638.     s ? {
  639.     s2 ||:= ="^"
  640.     s2 ||:= ="-"
  641.     while s2 ||:= tab(find("-")-1) do {
  642.         if (c1 := move(1), ="-",
  643.         c2 := move(1),
  644.         c1 << c2)
  645.         then every s2 ||:= char(ord(c1) to ord(c2))
  646.         else s2 ||:= 1(move(2), not(pos(0))) | err_out(s,2,"-")
  647.     }
  648.     s2 ||:= tab(0)
  649.     }
  650.     return s2
  651.  
  652. end
  653.  
  654.  
  655.  
  656. procedure tab_bal(l,i1,i2)
  657.     i := 0
  658.     i1_count := 0; i2_count := 0
  659.     while (i +:= 1) <= *l do {
  660.     case l[i] of {
  661.         i1  : i1_count +:= 1
  662.         i2  : i2_count +:= 1
  663.     }
  664.     if i1_count = i2_count
  665.     then suspend i
  666.     }
  667. end
  668.  
  669.  
  670. procedure match_positive_ints(l)
  671.     
  672.     # Matches the longest sequence of positive integers in l,
  673.     # beginning at l[1], which neither contains, nor is fol-
  674.     # lowed by a negative integer.  Returns the first position
  675.     # after the match.  Hence, given [55, 55, 55, -42, 55],
  676.     # match_positive_ints will return 3.  [55, -42] will cause
  677.     # it to fail rather than return 1 (NOTE WELL!).
  678.  
  679.     every i := 1 to *l do {
  680.     if l[i] < 0
  681.     then return (3 < i) - 1 | fail
  682.     }
  683.     return *l + 1
  684.  
  685. end
  686.  
  687.  
  688. procedure Ints2String(l)
  689.     tmp := ""
  690.     every tmp ||:= char(!l)
  691.     return tmp
  692. end
  693.  
  694.  
  695. procedure StripChar(s,s2)
  696.     if find(s2,s) then {
  697.     tmp := ""
  698.     s ? {
  699.         while tmp ||:= tab(find("s2"))
  700.         do tab(many(cset(s2)))
  701.         tmp ||:= tab(0)
  702.     }
  703.     }
  704.     return \tmp | s
  705. end
  706.