blob: 7eb175b60ec5ca25a39d44a41fc3a1d93ce2f079 [file] [log] [blame]
# Regular expression subroutines:
# sub(pat, repl, str): replace first occurrence of pattern in string
# gsub(pat, repl, str): replace all occurrences of pattern in string
# split(str, pat): split string using pattern as delimiter
import regex
# Replace first occurrence of pattern pat in string str by replacement
# repl. If the pattern isn't found, the string is returned unchanged.
# The replacement may contain references \digit to subpatterns and
# escaped backslashes. The pattern may be a string or an already
# compiled pattern.
def sub(pat, repl, str):
prog = compile(pat)
if prog.search(str) >= 0:
regs = prog.regs
a, b = regs[0]
str = str[:a] + expand(repl, regs, str) + str[b:]
return str
# Replace all (non-overlapping) occurrences of pattern pat in string
# str by replacement repl. The same rules as for sub() apply.
# Empty matches for the pattern are replaced only when not adjacent to
# a previous match, so e.g. gsub('', '-', 'abc') returns '-a-b-c-'.
def gsub(pat, repl, str):
prog = compile(pat)
new = ''
start = 0
first = 1
while prog.search(str, start) >= 0:
regs = prog.regs
a, b = regs[0]
if a == b == start and not first:
if start >= len(str) or prog.search(str, start+1) < 0:
break
regs = prog.regs
a, b = regs[0]
new = new + str[start:a] + expand(repl, regs, str)
start = b
first = 0
new = new + str[start:]
return new
# Split string str in fields separated by delimiters matching pattern
# pat. Only non-empty matches for the pattern are considered, so e.g.
# split('abc', '') returns ['abc'].
def split(str, pat):
prog = compile(pat)
res = []
start = next = 0
while prog.search(str, next) >= 0:
regs = prog.regs
a, b = regs[0]
if a == b:
next = next + 1
if next >= len(str):
break
else:
res.append(str[start:a])
start = next = b
res.append(str[start:])
return res
# Internal subroutines:
# compile(pat): compile a pattern, caching already compiled patterns
# expand(repl, regs, str): expand \digit escapes in replacement string
# Manage a cache of compiled regular expressions.
# If the pattern is a string a compiled version of it is returned.
# If the pattern has been used before we return an already compiled
# version from the cache; otherwise we compile it now and save the
# compiled version in the cache.
# Instead of a string, a compiled regular expression can also be
# passed.
# WARNING: if the pattern syntax is changed, the cache should be
# flushed!
cache = {}
def compile(pat):
if type(pat) <> type(''):
return pat # Assume it is a compiled regex
if cache.has_key(pat):
prog = cache[pat] # Get it from the cache
else:
prog = cache[pat] = regex.compile(pat)
return prog
# Expand \digit in the replacement.
# Each occurrence of \digit is replaced by the substring of str
# indicated by regs[digit]. To include a literal \ in the
# replacement, double it; other \ escapes are left unchanged (i.e.
# the \ and the following character are both copied).
def expand(repl, regs, str):
if '\\' not in repl:
return repl
new = ''
i = 0
while i < len(repl):
c = repl[i]; i = i+1
if c <> '\\' or i >= len(repl):
new = new + c
else:
c = repl[i]; i = i+1
if '0' <= c <= '9':
a, b = regs[eval(c)]
new = new + str[a:b]
elif c == '\\':
new = new + c
else:
new = new + '\\' + c
return new
# Test program, reads sequences "pat repl str" from stdin.
# Optional argument specifies pattern used to split lines.
def test():
import sys
if sys.argv[1:]:
delpat = sys.argv[1]
else:
delpat = '[ \t\n]+'
while 1:
if sys.stdin.isatty(): sys.stderr.write('--> ')
line = sys.stdin.readline()
if not line: break
if line[-1] == '\n': line = line[:-1]
fields = split(line, delpat)
if len(fields) <> 3:
print 'Sorry, not three fields'
print 'split:', `fields`
continue
[pat, repl, str] = split(line, delpat)
print 'sub :', `sub(pat, repl, str)`
print 'gsub:', `gsub(pat, repl, str)`