1688 lines
72 KiB
Plaintext
1688 lines
72 KiB
Plaintext
|
Scsh regular expressions
|
||
|
Olin Shivers
|
||
|
July 1998
|
||
|
Last Update: 99/7/12
|
||
|
|
||
|
* Todo
|
||
|
Module naming and structuring.
|
||
|
Documentation.
|
||
|
|
||
|
This document can be viewed in emacs outline mode, with *'s introducing
|
||
|
section headings -- just say M-x outline-mode in emacs.
|
||
|
|
||
|
* Table of contents
|
||
|
-------------------
|
||
|
Preamble: 100% and 80% solutions
|
||
|
Overview
|
||
|
Summary SRE syntax
|
||
|
Examples
|
||
|
A short tutorial
|
||
|
Discussion and design notes
|
||
|
Regexp functions
|
||
|
The scsh regexp ADT
|
||
|
Syntax-hacking tools
|
||
|
Acknowledgements
|
||
|
|
||
|
* Preamble: 100% and 80% solutions
|
||
|
----------------------------------
|
||
|
There's a problem with tool design in the free software and academic
|
||
|
community. The tool designers are usually people who are building tools for
|
||
|
some larger goal. For example, let's take the case of someone who wants to do
|
||
|
web hacking in Scheme. His Scheme system doesn't have a sockets interface, so
|
||
|
he sits down and hacks one up for his particular Scheme implementation. Now,
|
||
|
socket API's are not what this programmer is interested in; he wants to get on
|
||
|
with things and hack the exciting stuff -- his real interest is Web services.
|
||
|
So he does a quick 80% job, which is adequate to get him up and running, and
|
||
|
then he's on to his orignal goal.
|
||
|
|
||
|
Unfortunately, his quickly-built socket interface isn't general. It just
|
||
|
covers the bits this particular hacker needed for his applications. So the
|
||
|
next guy that comes along and needs a socket interface can't use this one.
|
||
|
Not only does it lack coverage, but the deep structure wasn't thought out well
|
||
|
enough to allow for quality extension. So *he* does his *own* 80%
|
||
|
implementation. Five hackers later, five different, incompatible, ungeneral
|
||
|
implementations had been built. No one can use each others code.
|
||
|
|
||
|
The alternate way systems like this end up going over a cliff is that they
|
||
|
get patched over and over again, and what one ends up with is more 80%
|
||
|
bandaids and 20% structured code. When systems like DOS evolve organically
|
||
|
into systems like Win95, it's unsuprising and unavoidable that what one
|
||
|
ends up with is a horrible design.
|
||
|
|
||
|
As an alternative to five hackers doing five 80% solutions of the same proble,
|
||
|
we would be much better off if each programmer picked a different task, and
|
||
|
really crushed it -- a 100% solution. Then each time a programmer solved a
|
||
|
problem, no one else would have to redo the effort. Of course, it's true that
|
||
|
100% solutions are significantly harder to design and build than 80%
|
||
|
solutions. But they have one tremendous labor-savings advantage: you don't
|
||
|
have to constantly reinvent the wheel. The up-front investment buys you
|
||
|
forward progress; you aren't trapped endlessly reinventing the same awkward
|
||
|
wheel.
|
||
|
|
||
|
Examples: I've done this three times. The first time was when I needed an
|
||
|
emacs mode in graduate school for interacting with Scheme processes. I looked
|
||
|
around, and I found a snarled up mess of many, many 80% solutions, some for
|
||
|
Lisp, some for Scheme, some for shells, some for gdb, and so forth. These
|
||
|
modes had all started off at some point as the original emacs shell.el mode,
|
||
|
then had been hacked up, eventually drifting into divergence. The keybindings
|
||
|
had no commonality. Some modes recovered old commands with a "yank" type form,
|
||
|
on c-c y. Some modes recovered old commands with m-p and m-n. It was hugely
|
||
|
confusing and not very functional.
|
||
|
|
||
|
The right thing to do was to carefully implement one, common base mode for
|
||
|
process interaction, and to carefully put in hooks for customising this base
|
||
|
mode into language-specific modes -- lisp, shell, Scheme, etc. So that's what
|
||
|
I did. I carefully went over the keybindings and functionality of all the
|
||
|
process modes I could find -- even going back to old Lisp Machine bindings for
|
||
|
Zwei -- and then I designed and implemented a base mode called comint. Now,
|
||
|
all process modes are implemented on top of comint, and no one, ever, has to
|
||
|
re-implement this code. Users only have to learn one set of bindings for
|
||
|
the common functions. Features put into the common code are available for free
|
||
|
to all the derived modes. Extensions are done, not by doing a completely new
|
||
|
design, but *in terms of* the original system -- it may not be perfect, but
|
||
|
it's good enough to allow people to move on and do other things.
|
||
|
|
||
|
The second time was the design of the Scheme Unix API found in scsh. Most
|
||
|
Schemes have a couple of functions for changing directory, some minimal socket
|
||
|
hacking, and perhaps forking off a shell command with the system() C function.
|
||
|
But no one has done a complete job, and the functions are never compatible.
|
||
|
So I sat down to do a careful, 100% job -- I wanted to cover everything in
|
||
|
section 2 of the Unix man pages, in a manner that was harmonious with the deep
|
||
|
structures of the Scheme language. As a design task, it was a tremendous
|
||
|
amount of work, taking several years, and multiple revisions. But now it's
|
||
|
done. Scsh's socket code, for instance, *completely* implements the socket
|
||
|
API. My hope in doing all this was that other people could profit from my
|
||
|
investment. If you are building your own Scheme system, *you* don't have to
|
||
|
put in the time. You can just steal the design. Or the code.
|
||
|
|
||
|
The regexp notation in this document represents a third attempt at this kind
|
||
|
of design. Looking back, I'm amazed at how much time I poured into the design,
|
||
|
not to mention the complete reference implementation. I sold myself on doing a
|
||
|
serious job with the philosophy of the 100% design -- the point is to save
|
||
|
other people the trouble. If the design is good enough, then instead of having
|
||
|
to do your own, you can steal mine and use the time saved... to do your own
|
||
|
100% design of something *else*, and fill in another gap.
|
||
|
|
||
|
I am not saying that these three designs of mine represent the last word on
|
||
|
the issues -- "100%" is really a bit of a misnomer, since no design is ever
|
||
|
really 100%. I would prefer to think of them as sufficiently good that they at
|
||
|
least present low-water marks -- future systems, I'd hope, can at least build
|
||
|
upon these designs, hopefully *in terms of* these designs. You don't ever have
|
||
|
to do *worse* -- you can just steal the design. If you don't have a
|
||
|
significantly better idea, I'd encourage you to adopt the design for the
|
||
|
benefits of compatibility. If you *do* have an improvement, email me about it,
|
||
|
so we can fold it in to the core design and *everyone* can win -- and we can
|
||
|
also make your improvement part of the standard, so that people can use your
|
||
|
good idea and *still* be portable.
|
||
|
|
||
|
But here's what I'd really like: instead of tweaking regexps, you go do your
|
||
|
own 100% design or two. Because I'd like to use them. If everyone does just
|
||
|
one, then that's all anyone has to do.
|
||
|
-Olin
|
||
|
|
||
|
|
||
|
* Overview
|
||
|
----------
|
||
|
This document describes the regular-expression system used in scsh.
|
||
|
The system is composed of several pieces:
|
||
|
|
||
|
- An s-expression notation for writing down general regular expressions.
|
||
|
In most systems, regexps are encoded as string literals. In scsh,
|
||
|
they are written using s-expressions. This notation has several
|
||
|
advantages over the traditional string-based notation; these advantages
|
||
|
are discussed in a following section.
|
||
|
|
||
|
- An abstract data type (ADT) representation for regexp values.
|
||
|
Traditional regular-expression systems compute regular expressions
|
||
|
from run-time values using strings. This can be awkward. Scsh, instead,
|
||
|
provides a separate data type for regexps, with a set of basic constructor
|
||
|
and accessor functions; regular expressions can be dynamically computed
|
||
|
and manipulated using these functions.
|
||
|
|
||
|
- Some tools that work on the regexp ADT: case-sensitve -> case-insensitive
|
||
|
regexp transform, a regexp simplifier, and so forth.
|
||
|
|
||
|
- Parsers and unparsers that can convert between external representations
|
||
|
and the regexp ADT. The supported external representations are
|
||
|
+ Posix strings
|
||
|
+ S-expression notation
|
||
|
Being able to convert regexps to Posix strings allows implementations
|
||
|
to implement regexp matching using standard Posix C-based engines.
|
||
|
|
||
|
- Macro support for the s-expression notation.
|
||
|
The RX macro provides a new special form that allows you to embed regexps in
|
||
|
the s-expression notation within a Scheme program. The macro parses the
|
||
|
regexp into the ADT, simplifies it, and translates it to a Posix strings,
|
||
|
which can be used by a traditional C-based regexp engine.
|
||
|
|
||
|
- Matchers
|
||
|
Spencer's Posix regexp engine is linked in to the runtime; the
|
||
|
regexp code uses this engine to provide text matching.
|
||
|
|
||
|
The regexp language supported is a complete superset of Posix functionality,
|
||
|
providing:
|
||
|
sequencing and choice (|)
|
||
|
repetition (*, +, ?, {m,n})
|
||
|
character clases and wildcard ([...], .)
|
||
|
beginning/end of string anchors
|
||
|
beginning/end of line anchors
|
||
|
beginning/end of word anchors
|
||
|
case-sensitivity control
|
||
|
submatch-marking (...)
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* Summary SRE syntax
|
||
|
--------------------
|
||
|
Here is a summary of the syntax; the next section is a friendlier tutorial
|
||
|
introduction.
|
||
|
|
||
|
SRE ::=
|
||
|
<string> literal match -- Interpreted relative to
|
||
|
the current case-sensitivity lexical context
|
||
|
(default is case-sensitive).
|
||
|
|
||
|
(<string1> <string2> ....) Set of chars, e.g., ("abc" "XYZ")
|
||
|
Interpreted relative to the current
|
||
|
case-sensitivity lexical context.
|
||
|
|
||
|
(* <sre> ...) 0 or more matches
|
||
|
(+ <sre> ...) 1 or more matches
|
||
|
(? <sre> ...) 0 or 1 matches
|
||
|
(= <n> <sre> ...) <n> matches
|
||
|
(>= <n> <sre> ...) <n> or more matches
|
||
|
(** <n> <m> <sre> ...) <n> to <m> matches
|
||
|
<n> and <m> are Scheme expressions producing non-negative integers.
|
||
|
<m> may also be #f, meaning "infinity"
|
||
|
|
||
|
(| <sre> ...) Choice (OR is R5RS symbol; | is unspecified)
|
||
|
(or <sre> ...)
|
||
|
|
||
|
(: <sre> ...) Sequence (SEQ is Common Lisp symbol)
|
||
|
(seq <sre> ...)
|
||
|
|
||
|
(submatch <sre> ...) Numbered submatch
|
||
|
(dsm <pre> <post> <sre> ...) Deleted submatches
|
||
|
<pre> and <post> are numerals.
|
||
|
|
||
|
(uncase <sre> ...) Case-folded match.
|
||
|
|
||
|
(w/case <sre> ...) Introduce a lexical case-sensitivity
|
||
|
(w/nocase <sre> ...) context.
|
||
|
|
||
|
,@<exp> Dynamically computed regexp.
|
||
|
,<exp> Same as ,@<exp>, but no submatch info.
|
||
|
<EXP> must produce a character, string,
|
||
|
char-set, or regexp.
|
||
|
|
||
|
bos eos Beginning/end of string
|
||
|
bol eol Beginning/end of line
|
||
|
|
||
|
bow eow Beginning/end of word
|
||
|
(word <sre> ...) (: bow <sre> ... eow)
|
||
|
(word+ <cset-sre> ...) (word (+ (& (| alphanumeric "_")
|
||
|
(| <cset-sre> ...))))
|
||
|
word (word+ any)
|
||
|
|
||
|
(posix-string <string>) Posix string -- for backwards compatibility.
|
||
|
|
||
|
<char> Singleton char set
|
||
|
<class-name> alphanumeric, whitespace, etc.
|
||
|
These two forms are interpreted subject to
|
||
|
the lexical case-sensitivity context.
|
||
|
|
||
|
(~ <cset-sre> ...) [^...] -- complement-of-union
|
||
|
(- <cset-sre> ...) Difference
|
||
|
(& <cset-sre> ...) Intersection
|
||
|
|
||
|
(/ <range-spec> ...) Character range -- interpreted subject to
|
||
|
the lexical case-sensitivy context.
|
||
|
|
||
|
<class-name> ::= any .
|
||
|
nonl [^\n]
|
||
|
|
||
|
(Posix character classes:)
|
||
|
lower-case upper-case alphabetic numeric alphanumeric
|
||
|
punctuation graphic whitespace printing control hex-digit
|
||
|
|
||
|
blank ascii (Gnu character classes)
|
||
|
|
||
|
(Shorter equivalent nicknames:)
|
||
|
lower upper alpha digit num alnum alphanum punct graph
|
||
|
space white print cntrl xdigit hex ascii
|
||
|
|
||
|
<range-spec> ::= <string> | <char>
|
||
|
The chars are taken in pairs to form inclusive ranges.
|
||
|
|
||
|
|
||
|
The ~, -, &, and word+ operators may only be applied to SRE's that specify
|
||
|
character sets. Here are the "type-checking" rules:
|
||
|
|
||
|
<cset-sre> ::= (~ <cset-sre> ...) Set complement-of-union
|
||
|
| (- <cset-sre> ...) Set difference
|
||
|
| (& <cset-sre> ...) Intersection
|
||
|
| (| <cset-sre> ...) Set union
|
||
|
| (/ <range-spec> ...) Range
|
||
|
|
||
|
| (<string>) Constant set
|
||
|
| <char> Singleton constant set
|
||
|
| <string> For 1-char string "c"
|
||
|
|
||
|
| <class-name> Constant set
|
||
|
|
||
|
| ,<exp> <exp> evals to a char-set,
|
||
|
| ,@<exp> char, single-char string,
|
||
|
or re-char-set regexp.
|
||
|
|
||
|
| (uncase <cset-sre>) Case-folding
|
||
|
| (w/case <cset-sre>)
|
||
|
| (w/nocase <cset-sre>)
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* Examples
|
||
|
----------
|
||
|
|
||
|
(- alpha ("aeiouAEIOU")) ; Various forms of non-vowel letter
|
||
|
(- alpha ("aeiou") ("AEIOU"))
|
||
|
(w/nocase (- alpha ("aeiou")))
|
||
|
(- (/"azAZ") ("aeiouAEIOU"))
|
||
|
(w/nocase (- (/"az") ("aeiou")))
|
||
|
|
||
|
;;; Upper-case letter, lower-case vowel, or digit
|
||
|
(| upper ("aeiou") digit)
|
||
|
(| (/"AZ09") ("aeiou"))
|
||
|
|
||
|
;;; Not an SRE, but Scheme code containing some embedded SREs.
|
||
|
(let* ((ws (rx (+ whitespace))) ; Seq of whitespace
|
||
|
(date (rx (: (| "Jan" "Feb" "Mar" ...) ; A month/day date.
|
||
|
,ws
|
||
|
(| ("123456789") ; 1-9
|
||
|
(: ("12") digit) ; 10-29
|
||
|
"30" "31"))))) ; 30-31
|
||
|
|
||
|
;; Now we can use DATE several times:
|
||
|
(rx ... ,date ... (* ... ,date ...)
|
||
|
... .... ,date))
|
||
|
|
||
|
;;; More Scheme code
|
||
|
(define (csl re) ; A comma-separated list of RE's is
|
||
|
(rx (| "" ; either zero of them (empty string), or
|
||
|
(: ,re ; one RE, followed by
|
||
|
(* ", " ,re))))) ; Zero or more comma-space-RE matches.
|
||
|
|
||
|
(csl (rx (| "John" "Paul" "George" "Ringo")))
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* A short tutorial
|
||
|
------------------
|
||
|
S-expression regexps are called "SRE"s. Keep in mind that they are *not*
|
||
|
Scheme expressions; they are another, separate notation that is expressed
|
||
|
using the underlying framework of s-expression list structure -- lists,
|
||
|
symbols, etc. SRE's can be *embedded* inside of Scheme expressions using
|
||
|
special forms that extend Scheme's syntax; there are places in the SRE
|
||
|
grammar where one may place a Scheme expression -- in these ways, SRE's and
|
||
|
Scheme expressions can be intertwined. But this isn't fundamental; SRE's may
|
||
|
be used in a completely Scheme-independent context. By simply restricting
|
||
|
the notation to eliminate two special Scheme-embedding forms, they can be
|
||
|
a completely independent notation.
|
||
|
|
||
|
|
||
|
** Constant strings
|
||
|
|
||
|
The simplest SRE is a string, denoting a constant regexp. For example, the SRE
|
||
|
"Spot"
|
||
|
matches only the string <<capital-S, little-p, little-o, little-t>>. There is
|
||
|
no interpretation of the characters in the string at all -- the SRE
|
||
|
".*["
|
||
|
matches the string <<period, asterisk, open-bracket>>.
|
||
|
|
||
|
|
||
|
** Simple character sets
|
||
|
|
||
|
To specify a set of characters, write a list whose single element is
|
||
|
a string containing the set's elements. So the SRE
|
||
|
("aeiou")
|
||
|
only matches a vowel. One way to think of this, notationally, is that the
|
||
|
set brackets are (" and ").
|
||
|
|
||
|
|
||
|
** Wild card
|
||
|
|
||
|
Another simple SRE is the symbol ANY, which matches any single character --
|
||
|
including newline and ASCII nul.
|
||
|
|
||
|
|
||
|
** Sequences
|
||
|
|
||
|
We can form sequences of SRE's with the SRE (: <sre> ...).
|
||
|
So the SRE
|
||
|
(: "x" any "z")
|
||
|
matches any three-character string starting with "x" and ending with "z".
|
||
|
As we'll see shortly, many SRE's have bodies that are implicit sequences of
|
||
|
SRE's, analogous to the manner in which the body of a Scheme LAMBDA or LET
|
||
|
expression is an implicit BEGIN sequence. The regexp (seq <sre> ...) is
|
||
|
completely equivalent to (: <sre> ...); it's included in order to have a
|
||
|
syntax that doesn't require : to be a legal symbol (e.g., for Common Lisp).
|
||
|
|
||
|
|
||
|
** Choices
|
||
|
|
||
|
The SRE (| <sre> ...) is a regexp that matches anything any of the
|
||
|
<sre> regexps match. So the regular expression
|
||
|
(| "sasha" "Pete")
|
||
|
matches either the string "sasha" or the string "Pete". The regexp
|
||
|
(| ("aeiou") ("0123456789"))
|
||
|
is the same as
|
||
|
("aeiou0123456789")
|
||
|
The regexp (or <sre> ...) is completely equivalent to (| <sre> ...);
|
||
|
it's included in order to have a syntax that doesn't require | to be a
|
||
|
legal symbol.
|
||
|
|
||
|
|
||
|
** Repetition
|
||
|
|
||
|
There are several SRE forms that match multiple occurences of a regular
|
||
|
expression. For example, the SRE (* <sre> ...) matches zero or more
|
||
|
occurences of the sequence (: <sre> ...). Here is the complete list
|
||
|
of SRE repetition forms:
|
||
|
|
||
|
SRE Means At least no more than
|
||
|
--- -------- -------- -----------
|
||
|
(* <sre> ...) zero-or-more 0 infinity
|
||
|
(+ <sre> ...) one-or-more 1 infinity
|
||
|
(? <sre> ...) zero-or-one 0 1
|
||
|
(= <from> <sre> ...) exactly-n <from> <from>
|
||
|
(>= <from> <sre> ...) n-or-more <from> infinity
|
||
|
(** <from> <to> <sre> ...) n-to-m <from> <to>
|
||
|
|
||
|
A <FROM> field is a Scheme expression that produces an integer.
|
||
|
A <TO> field is a Scheme expression that produces either an integer,
|
||
|
or false, meaning infinity.
|
||
|
|
||
|
While it is illegal for the <from> or <to> fields to be negative, it *is*
|
||
|
allowed for <from> to be greater than <to> in a ** form -- this simply
|
||
|
produces a regexp that will never match anything.
|
||
|
|
||
|
As an example, we can describe the names of car/cdr access functions
|
||
|
("car", "cdr", "cadr", "cdar", "caar" , "cddr", "caaadr", etc.) with
|
||
|
either of the SREs
|
||
|
(: "c" (+ (| "a" "d")) "r")
|
||
|
(: "c" (+ ("ad")) "r")
|
||
|
We can limit the a/d chains to 4 characters or less with the SRE
|
||
|
(: "c" (** 1 4 ("ad")) "r")
|
||
|
|
||
|
Some boundary cases:
|
||
|
(** 5 2 "foo") ; Will never match
|
||
|
(** 0 0 "foo") ; Matches the empty string
|
||
|
|
||
|
|
||
|
** Character classes
|
||
|
|
||
|
There is a special set of SRE's that form "character classes" -- basically,
|
||
|
a regexp that matches one character from some specified set of characters.
|
||
|
There are operators to take the intersection, union, complement, and
|
||
|
difference of character classes to produce a new character class. (Except
|
||
|
for union, these capabilities are not provided for general regexps as they
|
||
|
are computationally intractable in the general case.)
|
||
|
|
||
|
A single character is the simplest character class -- #\x is a character
|
||
|
class that matches only the character "x". A string that has only one
|
||
|
letter is also a character class -- "x" is the same SRE as #\x.
|
||
|
|
||
|
The character-set notation (<string>) we've seen is a primitive character
|
||
|
class, as is the wildcard ANY. When arguments to the choice operator, |, are
|
||
|
all character classes, then the choice form is itself a character-class.
|
||
|
So these SREs are all character-classes:
|
||
|
("aeiou")
|
||
|
(| #\a #\e #\i #\o #\u)
|
||
|
(| ("aeiou") ("1234567890"))
|
||
|
However, these SRE's are *not* character-classes:
|
||
|
"aeiou"
|
||
|
(| "foo" #\x)
|
||
|
|
||
|
The (~ <cset-sre> ...) char class matches one character not in the specified
|
||
|
classes:
|
||
|
(~ ("0248") ("1359"))
|
||
|
matches any character that is not a digit.
|
||
|
|
||
|
More compactly, we can use the / operator to specify character sets by giving
|
||
|
the endpoints of contiguous ranges, where the endpoints are specified by a
|
||
|
sequence of strings and characters. For example, any of these char classes
|
||
|
(/ #\A #\Z #\a #\z #\0 #\9)
|
||
|
(/ "AZ" #\a #\z "09")
|
||
|
(/ "AZ" #\a "z09")
|
||
|
(/"AZaz09")
|
||
|
matches a letter or a digit. The range endpoints are taken in pairs to
|
||
|
form inclusive ranges of characters. Note that the exact set of characters
|
||
|
included in a range is dependent on the underlying implementation's
|
||
|
character type, so ranges may not be portable across different implementations.
|
||
|
|
||
|
There is a wide selection of predefined, named character classes that may be
|
||
|
used. One such SRE is the wildcard ANY. NONL is a character class matching
|
||
|
anything but newline; it is equivalent to
|
||
|
(~ #\newline)
|
||
|
and is useful as a wildcard in line-oriented matching.
|
||
|
|
||
|
There are also predefined named char classes for the standard Posix and Gnu
|
||
|
character classes:
|
||
|
scsh name Posix/ctype Alt name Comment
|
||
|
-------------------------------------------------------
|
||
|
lower-case lower
|
||
|
upper-case upper
|
||
|
alphabetic alpha
|
||
|
numeric digit num
|
||
|
alphanumeric alnum alphanum
|
||
|
punctuation punct
|
||
|
graphic graph
|
||
|
blank (Gnu extension)
|
||
|
whitespace space white ("space" is deprecated.)
|
||
|
printing print
|
||
|
control cntrl
|
||
|
hex-digit xdigit hex
|
||
|
ascii (Gnu extension)
|
||
|
See the scsh character-set documentation or the Posix isalpha(3) man page
|
||
|
for the exact definitions of these sets.
|
||
|
|
||
|
You can use either the long scsh name or the shorter Posix and alternate names
|
||
|
to refer to these char classes. The standard Posix name "space" is provided,
|
||
|
but deprecated, since it is ambiguous. It means "whitespace," the set of
|
||
|
whitespace characters, not the singleton set of the #\space character.
|
||
|
If you want a short name for the set of whitespace characters, use the
|
||
|
char-class name "white" instead.
|
||
|
|
||
|
Char classes may be intersected with the operator (& <cset-sre> ...), and
|
||
|
set-difference can be performed with (- <cset-sre> ...). These operators are
|
||
|
particularly useful when you want to specify a set by negation *with respect
|
||
|
to a limited universe*. For example, the set of all non-vowel letters is
|
||
|
(- alpha ("aeiou") ("AEIOU"))
|
||
|
whereas writing a simple complement
|
||
|
(~ ("aeiouAEIOU"))
|
||
|
gives a char class that will match any non-vowel -- including punctuation,
|
||
|
digits, white space, control characters, and ASCII nul.
|
||
|
|
||
|
We can *compute* a char class by writing the SRE
|
||
|
,<cset-exp>
|
||
|
where <cset-exp> is a Scheme expression producing a value that can be
|
||
|
coerced to a character set: a character set, character, one-character
|
||
|
string, or char-class regexp value. This regexp matches any character
|
||
|
from the set.
|
||
|
|
||
|
The char-class SRE ,@<cset-exp> is entirely equivalent to ,<cset-exp>
|
||
|
when <cset-exp> produces a character set (but see below for the more
|
||
|
general non-char-class context, where there *is* a distinction between
|
||
|
,<exp> and ,@<exp>).
|
||
|
|
||
|
Example: An SRE that matches a lower-case vowel, upper-case letter,
|
||
|
or digit is
|
||
|
(| ("aeiou") (/"AZ09"))
|
||
|
or, equivalently
|
||
|
(| ("aeiou") upper-case numeric)
|
||
|
|
||
|
Boundary cases: the empty-complement char class
|
||
|
(~)
|
||
|
matches any character; it is equivalent to
|
||
|
any
|
||
|
The empty-union char class
|
||
|
(|)
|
||
|
never matches at all. This is rarely useful for human-written regexps,
|
||
|
but may be of occasional utility in machine-generated regexps, perhaps
|
||
|
produced by macros.
|
||
|
|
||
|
The rules for determining if an SRE is a simple, char-class SRE or a
|
||
|
more complex SRE form a little "type system" for SRE's. See the summary
|
||
|
section preceding this one for a complete listing of these rules.
|
||
|
|
||
|
** Case sensitivity
|
||
|
|
||
|
There are three forms that control case sensitivity:
|
||
|
(uncase <sre> ...)
|
||
|
(w/case <sre> ...)
|
||
|
(w/nocase <sre> ...)
|
||
|
|
||
|
UNCASE is a regexp operator producing a regexp that matches any
|
||
|
case permutation of any string that matches (: <sre> ...).
|
||
|
For example, the regexp
|
||
|
(uncase "foo")
|
||
|
matches the strings foo, foO, fOo, fOO, Foo, ...
|
||
|
|
||
|
Expressions in SRE notation are interpreted in a lexical case-sensitivy
|
||
|
context. The forms W/CASE and W/NOCASE are the scoping operators for this
|
||
|
context, which controls how constant strings and char-class forms are
|
||
|
interpreted in their bodies. So, for example, the regexp
|
||
|
(w/nocase "abc"
|
||
|
(* "FOO" (w/case "Bar"))
|
||
|
("aeiou"))
|
||
|
defines a case-insensitive match for all of its elements except for the
|
||
|
sub-element "Bar", which must match exactly capital-B, little-a, little-r.
|
||
|
The default, the outermost, top-level context is case sensitive.
|
||
|
|
||
|
The lexical case-sensitivity context affects the interpretation of
|
||
|
- constant strings, such as "foo"
|
||
|
- chars, such as #\x
|
||
|
- char sets, such as ("abc")
|
||
|
- ranges, such as (/"az")
|
||
|
that appear within that context. It does not affect dynamically computed
|
||
|
regexps -- ones that are introduced by ,<exp> and ,@<exp> forms. It does
|
||
|
not affect named char-classes -- presumably, if you wrote LOWER, you didn't
|
||
|
mean ALPHA.
|
||
|
|
||
|
UNCASE is *not* the same as W/NOCASE. To point up one distinction,
|
||
|
consider the two regexps
|
||
|
(uncase (~ "a"))
|
||
|
(w/nocase (~ "a"))
|
||
|
The regexp (~ "a") matches any character except "a" -- which means it *does*
|
||
|
match "A". Now, (uncase <re>) matches any case-permutation of a string that
|
||
|
<re> matches. (~ "a") matches "A", so (uncase (~ "a")) matches "A" and "a"
|
||
|
-- and, for that matter, every other character. So (uncase (~ "a")) is
|
||
|
equivalent to ANY.
|
||
|
|
||
|
In contrast, (w/nocase (~ "a")) establishes a case-insensitive lexical
|
||
|
context in which the "a" is interpreted, making the SRE equivalent to
|
||
|
(~ ("aA")).
|
||
|
|
||
|
|
||
|
** Dynamic regexps
|
||
|
|
||
|
SRE notation allows you to compute parts of a regular expression
|
||
|
at run time. The SRE
|
||
|
,<exp>
|
||
|
is a regexp whose body <exp> is a Scheme expression producing a
|
||
|
string, character, char-set, or regexp as its value. Strings and
|
||
|
characters are converted into constant regexps; char-sets are converted
|
||
|
into char-class regexps; and regexp values are substituted in place.
|
||
|
So we can write regexps like this
|
||
|
(: "feeding the "
|
||
|
,(if (> n 1) "geese" "goose"))
|
||
|
This is how you can drop computed strings, such as someone's name,
|
||
|
or the decimal numeral for a computed number, into a complex regexp.
|
||
|
|
||
|
If we have a large, complex regular expression that is used multiple
|
||
|
times in some other, containing regular expression, we can name it, using
|
||
|
the binding forms of the embedding language (e.g., Scheme), and refer to
|
||
|
it by name in the containing expression. E.g.: The Scheme expression
|
||
|
|
||
|
(let* ((ws (rx (+ whitespace))) ; Seq of whitespace
|
||
|
|
||
|
(date (rx (: (| "Jan" "Feb" "Mar" ...) ; A month/day date.
|
||
|
,ws
|
||
|
(| ("123456789") ; 1-9
|
||
|
(: ("12") digit) ; 10-29
|
||
|
"30" "31"))))) ; 30-31
|
||
|
|
||
|
;; Now we can use DATE several times:
|
||
|
(rx ... ,date ... (* ... ,date ...)
|
||
|
... .... ,date))
|
||
|
|
||
|
where the (RX <sre> ...) macro is the Scheme special form that produces
|
||
|
a Scheme regexp value given a body in SRE notation.
|
||
|
|
||
|
As we saw in the char-class section, if a dynamic regexp is used
|
||
|
in a char-class context (e.g., as an argument to a ~ operation),
|
||
|
the expression must be coercable not merely to a general regexp,
|
||
|
but to a character set -- so it must be either a singleton string,
|
||
|
a character, a scsh char set, or a char-class regexp.
|
||
|
|
||
|
We can also define and use functions on regexps in the host language.
|
||
|
For example,
|
||
|
|
||
|
(define (csl re) ; A comma-separated list of RE's is either
|
||
|
(rx (| "" ; zero of them (empty string), or
|
||
|
(: ,re ; RE followed by
|
||
|
(* ", " ,re))))); zero or more comma-space-RE matches.
|
||
|
|
||
|
(rx ... ,date ...
|
||
|
,(csl (rx (| "John" "Paul" "George" "Ringo")))
|
||
|
...
|
||
|
... ,(csl date) ...)
|
||
|
|
||
|
I leave the extension of CSL to allow for an optional "and" between the last
|
||
|
two matches as an exercise for the interested reader (e.g., to match
|
||
|
"John, Paul, George and Ringo").
|
||
|
|
||
|
Note, in passing, one of the nice features of SRE notation: SREs can
|
||
|
be commented.
|
||
|
|
||
|
When we embed a computed regexp inside another regular expression with
|
||
|
the ,<exp> form, we must specify how to account for the submatches that
|
||
|
may be in the computed part. For example, suppose we have the regexp
|
||
|
(rx (submatch (* "foo"))
|
||
|
(submatch (? "bar"))
|
||
|
,(f x)
|
||
|
(submatch "baz"))
|
||
|
It's clear that the submatch for the (* "foo") part of the regexp is
|
||
|
submatch #1, and the (? "bar") part is submatch #2. But what number
|
||
|
submatch is the "baz" submatch? It's not clear. Suppose the Scheme
|
||
|
expression (f x) produces a regular expression that itself has 3
|
||
|
subforms. Are these counted (making the "baz" submatch #6), or not
|
||
|
counted (making the "bar" submatch #3)?
|
||
|
|
||
|
SRE notation provides for both possibilities. The SRE
|
||
|
,<exp>
|
||
|
does *not* contribute its submatches to its containing regexp; it
|
||
|
has zero submatches. So one can reliably assign submatch indices to
|
||
|
forms appearing after a ,<exp> form in a regexp.
|
||
|
|
||
|
On the other hand, the SRE
|
||
|
,@<exp>
|
||
|
"splices" its resulting regexp into place, *exposing* its submatches
|
||
|
to the containing regexp. This is useful if the computed regexp is defined
|
||
|
to produce a certain number of submatches -- if that is part of <exp>'s
|
||
|
"contract."
|
||
|
|
||
|
|
||
|
** String, line, and word units
|
||
|
|
||
|
The regexps BOS and EOS match the empty string at the beginning and end of
|
||
|
the string, respectively.
|
||
|
|
||
|
The regexps BOL and EOL match the empty string at the beginning and end of
|
||
|
a line, respectively. A line begins at the beginning of the string, and
|
||
|
just after every newline character. A line ends at the end of the string,
|
||
|
and just before every newline character. The char class NONL matches any
|
||
|
character except newline, and is useful in conjunction with line-based
|
||
|
pattern matching.
|
||
|
|
||
|
The regexps BOW and EOW match the empty string at the beginning and end of
|
||
|
a word, respectively. A word is a contiguous sequence of characters that
|
||
|
are either alphanumeric or the underscore character.
|
||
|
|
||
|
The regexp (WORD <sre> ...) surrounds the sequence (: <sre> ...)
|
||
|
with bow/eow delimiters. It is equivalent to
|
||
|
(: bow <sre> ... eow)
|
||
|
|
||
|
The regexp (WORD+ <cset-sre> ...) matches a word whose body is one or
|
||
|
more word characters also in one of the <class-arg> classes. It is equivalent
|
||
|
to
|
||
|
(word (+ (& (| alphanumeric "_")
|
||
|
(| <cset-sre> ...))))
|
||
|
For example, a word not containing x, y, or z is
|
||
|
(word+ (~ ("xyz")))
|
||
|
|
||
|
The regexp WORD matches one word; it is equivalent to
|
||
|
(word+ any)
|
||
|
|
||
|
[Note: BOL and EOL are not supported by scsh's current regexp search engine,
|
||
|
which is Spencer's Posix matcher. This is the only element of the notation
|
||
|
that is not supported by the current scsh reference implementation.]
|
||
|
|
||
|
|
||
|
** Miscellaneous elements
|
||
|
|
||
|
*** Posix string notation
|
||
|
|
||
|
The SRE (posix-string <string>), where <string> is a string literal (*not*
|
||
|
a general Scheme expression that evaluates to a string), allows one to use
|
||
|
Posix string notation for a regexp. It's intended as backwards compatibility
|
||
|
and is deprecated. Example: (posix-string "[aeiou]+|x*|y{3,5}") matches a
|
||
|
string of vowels, a possibly empty string of x's, or three to five y's.
|
||
|
|
||
|
Parens are used ambiguously in Posix notation -- both for grouping
|
||
|
and submatch marking. The (posix-string <string>) form makes the
|
||
|
conservative assumption: all parens introduce submatches.
|
||
|
|
||
|
*** Deleted submatches
|
||
|
|
||
|
DSM's are a subtle feature that are never required in expressions written
|
||
|
by humans. They can be introduced by the simplifier when reducing
|
||
|
regular expressions to simpler equivalents, and are included in the
|
||
|
syntax to give it expressibility spanning the full regexp ADT. They
|
||
|
may appear when unparsing simplified regular expressions that have
|
||
|
been run through the simplifier; otherwise you are not likely to see them.
|
||
|
Feel free to skip this section.
|
||
|
|
||
|
The regexp simplifier can sometimes eliminate entire sub-expressions from a
|
||
|
regexp. For example, the regexp
|
||
|
(: "foo" (** 0 0 "apple") "bar")
|
||
|
can be simplified to
|
||
|
"foobar"
|
||
|
since (** 0 0 "apple") will always match the empty string. The regexp
|
||
|
(| "foo"
|
||
|
(: "Richard" (|) "Nixon")
|
||
|
"bar")
|
||
|
can be simplified to
|
||
|
(| "foo" "bar")
|
||
|
The empty choice (|) can't match anything, so the whole
|
||
|
(: "Richard" (|) "Nixon")
|
||
|
sequence can't match, and we can remove it from the choice.
|
||
|
|
||
|
However, if deleting part of a regular expression removes a submatch
|
||
|
form, any following submatch forms will have their numbering changed,
|
||
|
which would be an error. For example, if we simplify
|
||
|
(: (** 0 0 (submatch "apple"))
|
||
|
(submatch "bar"))
|
||
|
to
|
||
|
(submatch "bar")
|
||
|
then the "bar" submatch changes from submatch 2 to submatch 1 -- so this
|
||
|
is not a legal simplification.
|
||
|
|
||
|
When the simplifier deletes a sub-regexp that contains submatches,
|
||
|
it introduces a special regexp form to account for the missing,
|
||
|
deleted submatches, thus keeping the submatch accounting correct.
|
||
|
(dsm <pre> <post> <sre> ...)
|
||
|
is a regexp that matches the sequence (: <sre> ...). <Pre> and
|
||
|
<post> are integer constants. The DSM form introduces <pre> deleted
|
||
|
submatches before the body, and <post> deleted submatches after the
|
||
|
body. If the body (: <sre> ...) itself has body-sm submatches,
|
||
|
then the total number of submatches for the DSM form is
|
||
|
(+ <pre> body-sm <post>)
|
||
|
These extra, deleted submatches are never assigned string indices in any
|
||
|
match values produced when matching the regexp against a string.
|
||
|
|
||
|
As examples,
|
||
|
(| (: (submatch "Richard") (|) "Nixon")
|
||
|
(submatch "bar"))
|
||
|
can be simplified to
|
||
|
(dsm 1 0 (submatch "bar"))
|
||
|
|
||
|
The regexp
|
||
|
(: (** 0 0 (submatch "apple"))
|
||
|
(submatch "bar"))
|
||
|
can be simplified to
|
||
|
(dsm 1 0 (submatch "bar"))
|
||
|
|
||
|
|
||
|
** Embedding regexps within Scheme programs
|
||
|
|
||
|
SRE's can be placed in a Scheme program using the (rx <sre> ...)
|
||
|
Scheme form, which evaluates to a Scheme regexp value.
|
||
|
|
||
|
|
||
|
** Static and dynamic regexps
|
||
|
|
||
|
We separate SRE expressions into two classes: static and dynamic
|
||
|
expressions. A *static* expression is one that has no run-time dependencies;
|
||
|
it is a complete, self-contained description of a regular set. A *dynamic*
|
||
|
expression is one that requires run-time computation to determine the
|
||
|
particular regular set being described. There are two places where one can
|
||
|
embed run-time computations in an SRE:
|
||
|
- The <from> or <to> repetition counts of **, =, and >= forms;
|
||
|
- ,<exp> and ,@<exp> forms.
|
||
|
A static SRE is one that does not contain any ,<exp> or ,@<exp> forms,
|
||
|
and whose **, =, and >= forms all contain constant repetition counts.
|
||
|
|
||
|
Scsh's RX macro is able, at macro-expansion time, to completely parse,
|
||
|
simplify and translate any static SRE into the equivalent Posix string
|
||
|
which is used to drive the underlying C-based matching engine; there is
|
||
|
no run-time overhead. Dynamic SRE's are partially simplified and then expanded
|
||
|
into Scheme code that constructs the regexp at run-time.
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* Discussion and design notes
|
||
|
-----------------------------
|
||
|
Many considerations drove the design of the SRE notation. I took
|
||
|
advantage of ideas found in the s-expression notations of Manuel
|
||
|
Serrano's Bigloo systems-programming Scheme implementation and Michael
|
||
|
Sperber's Scheme 48 design. I also considered features found in many
|
||
|
traditional regexp implementations, including the Posix standard, Henry
|
||
|
Spencer's package, Tom Lord's rx system, gnu regex, and Perl's system.
|
||
|
Features that didn't make it into the scsh system are not there for a reason.
|
||
|
|
||
|
Lord's package provided the name for the basic SRE macro, which is
|
||
|
agreeably brief.
|
||
|
|
||
|
What follows is a loose collection of design notes.
|
||
|
|
||
|
** No lazy repetition operators
|
||
|
|
||
|
SRE notation does not provide lazy repeat forms, of the kind found in perl.
|
||
|
Lazy repeat forms have problems. In principle, a regexp doesn't specify a
|
||
|
matching algorithm, it specifies a *set of strings*. Lazy submatches violate
|
||
|
this principle. Providing lazy submatches forces a particular matching
|
||
|
algorithm: an NDFA-based backtracking search engine -- you can't use a
|
||
|
fast DFA no-backtracking engine. I chose to restrict the notation to keep
|
||
|
it algorithm-independent. (This isn't strictly true -- we sleaze in one area:
|
||
|
submatches require an NDFA searcher, but the standard C-based DFA engines
|
||
|
have clever hacks for dealing with this.)
|
||
|
|
||
|
Also, lazy submatches can't be implemented with a Posix search engine,
|
||
|
and I wanted to allow for this.
|
||
|
|
||
|
Note that an alternative to perl's lazy repeat forms would be to have
|
||
|
a flag in the match and search functions telling it to provide either the
|
||
|
leftmost longest or the leftmost shortest match. This is a *global* property
|
||
|
of the whole regexp, not of a particular part, and can be easily provided
|
||
|
by either DFA or NDFA engines.
|
||
|
|
||
|
I suspect this would handle many (most? all?) of the cases where perl hackers
|
||
|
want lazy repetition operators. But it is more in the spirit of regexps,
|
||
|
where one notates a *regular set* of strings with a regexp, then asks the
|
||
|
matcher to find a match -- it's the matcher's business how it will choose
|
||
|
from multiple matches to strings in the set.
|
||
|
|
||
|
Finally, my suspicion is that the sort of things people do with lazy
|
||
|
repetition operators (e.g., match delimited <foo> ... </foo> regions
|
||
|
in html text) are abusing regexps, pushing them beyond their real
|
||
|
capabilities, which is asking for trouble. Strings of balanced delimiters
|
||
|
aren't regular; you should be using more powerful parsing tools if this
|
||
|
is what you want to do. Don't break regexps to handle this case in a
|
||
|
fragile way; design a different parsing tool. For an example of a more
|
||
|
powerful parsing tool, see the elegant parser tool, READ/RP, in Serrano's
|
||
|
Scheme system bigloo.
|
||
|
|
||
|
|
||
|
** No named submatches
|
||
|
|
||
|
One might want a feature wherein we could *name* our submatches, instead
|
||
|
of referring to them by their numerical index in the regexp. Perhaps
|
||
|
something like
|
||
|
(: ...
|
||
|
(named-match phone-num (= 3 digit) "-" (= 4 digit))
|
||
|
...)
|
||
|
which would somehow "bind" phone-num to the substring matching the
|
||
|
seven-digit phone number. This is awkward. The problem is that binding
|
||
|
introduces issues of scope -- what is the scope of an identifier "bound"
|
||
|
in a regexp? Suppose the SRE is used in a Scheme form to produce a first-class
|
||
|
regexp value, which can be passed around in and out of various scopes?
|
||
|
Clearly, somehow using Scheme's variables isn't going to work. If one then
|
||
|
turns to symbol-indexed tables, one is leaving the language-level for
|
||
|
binding, and moving to run-time data values. This is inefficient and
|
||
|
awkward. Furthermore, what are the precedence rules when the same identifier
|
||
|
is bound multiple times in the same SRE?
|
||
|
|
||
|
There's no shame in positional, indexed references. It's how parameters
|
||
|
are passed in Scheme procedure calls. It's the natural mechanism that
|
||
|
arises for regexps, so that's what is provided.
|
||
|
|
||
|
One might consider a hairy named-submatch system where one would specify in
|
||
|
a *single form* (1) an SRE with named submatches, (2) a string to match/search,
|
||
|
and (3) a body of code to be conditionally evaluated in the scope of the bound
|
||
|
names. This would use names in a way that was tightly integrated with Scheme,
|
||
|
which is good. But you must now wrestle with some very tricky issues:
|
||
|
|
||
|
- Are the names bound to substrings or pairs of start/stop indices into
|
||
|
the source text? Sometimes you want one, sometimes the other.
|
||
|
|
||
|
- You have to give up on passing regexps around as first-class values.
|
||
|
SRE's are now scope-introducing, variable-binding syntax, like
|
||
|
LET or LAMBDA. So much for ,<exp> and the power of dynamic regexps.
|
||
|
|
||
|
- What to do about a name that appears multiple times in the regexp?
|
||
|
|
||
|
I did not go down this path. Be one with the system; don't fight against it.
|
||
|
(However, see the LET-MATCH, IF-MATCH, and MATCH-COND forms, which provide
|
||
|
matching, positional name-binding, and control transfers in a consistent
|
||
|
manner.)
|
||
|
|
||
|
|
||
|
** No intersection or negation operators
|
||
|
|
||
|
The SRE notation supports a general union operator, the choice form
|
||
|
(| <sre> ...). However, the rest of the set algebra -- negation,
|
||
|
intersection, and subtraction -- is restricted to character sets. Wouldn't
|
||
|
it be nice to extend these operators to apply to general regexps? After
|
||
|
all, regular sets *are* closed under these operations, and they are useful,
|
||
|
so they should be in the toolkit.
|
||
|
|
||
|
They aren't in for two reasons:
|
||
|
1. Combinatoric explosion
|
||
|
Intersection forces you to convert your regexp to a DFA, which can entail
|
||
|
exponential growth in the state space. After the exponential explosion,
|
||
|
you work in the cross-product of the two DFA's state spaces, for another
|
||
|
multiplicative factor. It's hard to control. Negation presents similar
|
||
|
difficulties, in either time (DFA) or space (NDFA).
|
||
|
|
||
|
2. Standard C engines don't provide it. (Because of reason #1.)
|
||
|
|
||
|
Sure, it'd be great to have them anyway. What you probably want is
|
||
|
a direct-in-Scheme DFA/NDFA toolkit. Then you can take your regexp,
|
||
|
convert it to a DFA, do the intersection, and either interpret the
|
||
|
result machine, or translate it to Scheme code for direct execution.
|
||
|
The programmer would have to take responsibility for managing the
|
||
|
potential for combinatorial explosion.
|
||
|
|
||
|
It's a great idea. You do it. I was careful to design the notation to
|
||
|
allow for it -- you don't even have to introduce new operators, just
|
||
|
lift the char-class "typing" restriction on the existing ~, -, and &
|
||
|
ops.
|
||
|
|
||
|
Note that just by allowing general set operations on character classes,
|
||
|
we're still way out in front of traditional notation, which doesn't
|
||
|
provide this feature at all.
|
||
|
|
||
|
|
||
|
** No name-binding forms
|
||
|
|
||
|
SRE notation doesn't have a form for binding names to regexps; this
|
||
|
is punted to the host language by way of the ,<exp> mechanism. This
|
||
|
is arguably a lack from the point of view of SRE's as a completely
|
||
|
standalone, static notation, independent of their embedding language.
|
||
|
But enough is enough.
|
||
|
|
||
|
|
||
|
** No SRE macros
|
||
|
|
||
|
It's a shame that we can't provide a means of allowing programmers to define
|
||
|
their own SRE macros, by which I do *not* mean Scheme expressions that contain
|
||
|
SRE forms, but *new* classes of SRE form, beyond (: ...), (| ...) and friends.
|
||
|
For example, the (WORD+ <cset-sre> ...) form is not really primitive; it
|
||
|
can be defined by way of expansion to
|
||
|
(: bow (& (| alphanum "_") (| <cset-sre> ...)) eow)
|
||
|
A given task might profit from allowing the programmer to extend SRE's
|
||
|
by way of rewriting forms into the base SRE notation. But Scheme does
|
||
|
not support this -- it only provides macros for Scheme expressions.
|
||
|
Too bad.
|
||
|
|
||
|
|
||
|
** No back-references
|
||
|
|
||
|
Completely non-regular -- there's a reason these were dropped from Posix'
|
||
|
"extended" (= "modern") regexp notation.
|
||
|
|
||
|
|
||
|
** No "syntax classes"
|
||
|
|
||
|
"Syntax classes" are a gnu-emacs feature for describing certain character
|
||
|
sets. SRE already has a powerful set of character-set operators, and the
|
||
|
whole notion of "syntax class" is emacs specific. So they weren't included.
|
||
|
|
||
|
|
||
|
** Range notation
|
||
|
|
||
|
One might consider it more "Schemey" to have the char-class range notation
|
||
|
specify the from/to pairs as two-element lists, e.g.
|
||
|
(/ (#\a #\z) (#\0 #\9))
|
||
|
or maybe even
|
||
|
(/ (#\a . #\z) (#\0 . #\9))
|
||
|
If we do things this way, the structure of the s-expression more closely
|
||
|
mirrors the underlying structure of the form. Well, yes. But it's hard
|
||
|
to read -- I claim that ripping out all the sharp signs, backslashes, dots
|
||
|
and extra parens is much easier on your eyes:
|
||
|
(/"az09")
|
||
|
and I am unable to see what the extra pairing overhead buys you over
|
||
|
and above gratuitous notational bloat. Remember that this notation is
|
||
|
designed primarily for *human* producers and consumers. The *machine*
|
||
|
doesn't see the *notation*; it sees the nice, regular ADT.
|
||
|
|
||
|
|
||
|
** Big notation
|
||
|
|
||
|
SRE notation is baroque -- there are a lot of ways to write the same
|
||
|
regexp. This is not accidental. The idea is to make a notation that is as
|
||
|
expressive as possible for human-written documents. The ADT, in contrast, is
|
||
|
simple and spare -- it is designed to be operated upon by programs.
|
||
|
|
||
|
|
||
|
** Implementation complexity
|
||
|
|
||
|
This implementation is much more complex than I'd like; there are three main
|
||
|
reasons. The first reason is the strategy of parsing SRE's, not directly to
|
||
|
Scheme code, but instead to regexp records, which can then be simplified and
|
||
|
then unparsed to Scheme code (or other forms, such as SRE or Posix string).
|
||
|
Centering the design on the ADT was the right thing to do, as it enables other
|
||
|
unparsers to use the same "front-end" parser -- for example, if someone wanted
|
||
|
to write a macro that expanded (static) SRE's into a LETREC directly
|
||
|
implementing the DFA, it would be much easier to work from the ADT than
|
||
|
directly from the SRE form. It also enables the macro to apply a lot of
|
||
|
processing to the form at compile-time, such as the simplifier, giving us a
|
||
|
sleazy but effective form of partial evaluation. However... we pay a price in
|
||
|
complexity to do things this way. Code that processes regexp records that
|
||
|
might be used *at macro-expansion time* must be written to tolerate the
|
||
|
presence of Scheme *code* in record fields that would ordinarily only contain
|
||
|
Scheme *values* -- for example, the FROM and TO fields of the repeat record. I
|
||
|
got a lot of code reuse by making these records do double-duty as both the
|
||
|
regexp ADT *and* the compiler's syntax tree (AST) for expressions computing
|
||
|
these values -- but I had to get the code that did double-duty *just right*,
|
||
|
which meant being careful to add code/value checks on all accesses to these
|
||
|
fields.
|
||
|
|
||
|
(Note that *user* code manipulates run-time regexp values, and so will
|
||
|
never see anything but values in these fields.)
|
||
|
|
||
|
The second reason is the inherent difficulty in translating general character
|
||
|
sets to [...] Posix character classes, which seem simple at first, but turn
|
||
|
out to have very awkward special-case restrictions on the grammar -- I discuss
|
||
|
this at length in a later section. The char-set rendering code is made more
|
||
|
complex than it could have been because I made an effort to render them
|
||
|
into concise, readable descriptions, using ranges and ^ negation where
|
||
|
possible to minimise the notation.
|
||
|
|
||
|
The final major influence on the code complexity is all the bookkeeping that
|
||
|
is involved in submatches and DSM's. Tracking DSM's complicates just about all
|
||
|
parts of the system: the data-structures, the simplifier, the parser, and so
|
||
|
on. It's really amazing how this one feature comes to dominate all the
|
||
|
processing. But it has to be done. Submatches are an indispensable part of the
|
||
|
way we use regexps. Simplification -- the process that introduces DSM's -- is
|
||
|
not an option: SRE syntax is more general than traditional syntax, and permits
|
||
|
authors to write expressions that don't have representations in traditional
|
||
|
syntax. Simplifying the regular expression rewrites these un-translatable
|
||
|
cases into equivalent cases that *are* translatable. So we must simplify
|
||
|
before translating to Posix strings, hence we are stuck with DSM's.
|
||
|
|
||
|
I went to the trouble of doing a full-bore implementation to have a
|
||
|
reference implementation for others to steal. So the complexity of the
|
||
|
coding shouldn't throw anyone who wants to use this notation; it's all
|
||
|
been implemented.
|
||
|
|
||
|
|
||
|
** Should anchors be primitive regexps or operators?
|
||
|
|
||
|
There are two ways to do anchors, such as BOL and EOL, in an
|
||
|
s-expression syntax. One way is to have them be primitive regexps
|
||
|
that match the empty string in a particular context (which is how it
|
||
|
is done in SRE syntax). The alternate method, found in some other designs
|
||
|
I have studied, is to have anchors be *operators* with bodies, e.g.
|
||
|
(at-bos "foo" (* " ") "bar")
|
||
|
which would match a "foo<white-space>bar" string, anchoring the match
|
||
|
to the beginning of the string. This works reasonably well for
|
||
|
beginning-of-element anchors, but with end-of-element anchors, it puts
|
||
|
the operator on the wrong side -- the left side -- of the regexp body:
|
||
|
(at-eol "foo" (* " ") "bar")
|
||
|
The end-of-line in this pattern occurs after the "bar", but the operator's
|
||
|
way over on the other side of the regexp body. This gets especially ugly
|
||
|
when we want to delimit both sides of the body:
|
||
|
(at-eol (at-bos "foo" (* " ") "bar"))
|
||
|
Too many parens; too much nesting; too hard to read. I went with the
|
||
|
magic-empty-string primitive regexp model:
|
||
|
(: bos "foo" (* " ") "bar" eos)
|
||
|
|
||
|
|
||
|
** Character class operators
|
||
|
|
||
|
In an earlier version of this notation, I had a distinct subnotation
|
||
|
for character classes, with a distinct non-| operator for char-set
|
||
|
union. This provided a simple, syntactic way to separate the char-set
|
||
|
set algebra from the other operations of the language, to ensure you
|
||
|
didn't try to complement or intersect general regexps. The char-set
|
||
|
operators were IN, -, &, ~ for union, difference, intersection, and
|
||
|
complement. Inside these operators, simple strings stood for character
|
||
|
sets. So we'd write the any-vowel SRE (in "aeiou") instead of ("aeiou").
|
||
|
|
||
|
Shifting over to a distinct SRE form for constant char sets -- ("aeiou") --
|
||
|
allowed me throw out the whole syntactic division between char-sets and
|
||
|
other regexps, replacing this division with a "type system" restricting
|
||
|
non-union set operations to char-sets. This seems like a big improvement
|
||
|
for two reasons:
|
||
|
- The syntax was simplified and made less context-dependent.
|
||
|
- Should we ever wish to extend the regexp system to allow
|
||
|
for set operations on general regexps, the notation doesn't
|
||
|
have to be changed or extended at all.
|
||
|
|
||
|
|
||
|
** Operator names
|
||
|
|
||
|
Always a painful task. Here are some random notes on my choices.
|
||
|
|
||
|
Before choosing W/CASE and W/NOCASE, I considered
|
||
|
CASE-SENSITIVE and CASE-INSENSITIVE
|
||
|
and
|
||
|
CASE0 CASE1
|
||
|
but the former is far too long, and the latter was insufficiently clear.
|
||
|
One early reviewer (Rees) asked me where were CASE2, CASE3, et al. So I went
|
||
|
with W/CASE and W/NOCASE.
|
||
|
|
||
|
I considered CASE-FOLD before selecting UNCASE. UNCASE is shorter and
|
||
|
seems no more or less clear.
|
||
|
|
||
|
I considered &/+/~/- for the set algebra ops -- it's a nice, consistent, terse
|
||
|
operator set. This would give us an R5RS-sanctioned operator for union, as an
|
||
|
alternative to the slightly iffy |. But if we use + for union, there is some
|
||
|
pressure to use * for intersection, by way of analogy with mod-2
|
||
|
addition/multiplication. But * is already assigned to a repetition operator.
|
||
|
Furthermore, + is also already taken, by the 1..infinity repetition
|
||
|
operator. Not only are these * and + assignments firmly ingrained in
|
||
|
traditional syntax, I couldn't come up with a good, short alternative for the
|
||
|
repetition operators, so I kept them + and *. | is also the and/or mate of &
|
||
|
in C, so it's natural to pair them in an and/or intersection/union manner.
|
||
|
|
||
|
I also considered and/or/not/diff for the char-set algebra ops. But
|
||
|
the names are too long for such common primitives.
|
||
|
|
||
|
|
||
|
** No collating elements
|
||
|
|
||
|
Posix has this completely opaque feature of character sets called "collating
|
||
|
elements." It's some mechanism whereby you can, in a locale-independent way,
|
||
|
get a pair of characters to sort as one character, or have a German eszet
|
||
|
(the one that looks like a beta) character sort like a pair of s's. This stuff,
|
||
|
which I am unable to understand without bleeding from the nose and ears, is
|
||
|
part of the full Posix regexp spec -- you can say things like [[=ch=]]
|
||
|
and get a character class which will actually match two characters out of
|
||
|
a target string, if your locale defines a collating element <ch>.
|
||
|
|
||
|
My system doesn't support this at all. SRE char-class expressions are
|
||
|
rendered to scsh character sets, which are then rendered into [...]
|
||
|
sets containing the elements of those sets. The rendered [...] expressions
|
||
|
never contain [..], [==], or [::] elements.
|
||
|
|
||
|
As I discuss below, I'm willing to support a super-ASCII character type,
|
||
|
such as Unicode or big-5 or latin-1. What I find objectionable is the
|
||
|
idea of "collation elements" that can match more than one character in
|
||
|
a row.
|
||
|
|
||
|
I'm open to more suggestions on this front.
|
||
|
|
||
|
|
||
|
** Character set dependencies
|
||
|
|
||
|
If you wanted to do a Unicode version of my package, you'd have to redo the
|
||
|
scsh character-set machinery, and also the char-set unparsers in the regexp
|
||
|
backends. If you wanted to do a Latin-1 version, you'd need to slightly tune
|
||
|
scsh's primitive character sets (such as char-set:alphabetic). The SRE->Posix
|
||
|
string character-class rendering code is written to be as independent as
|
||
|
possible of the character type, but it has some dependencies, and would need
|
||
|
to be tuned.
|
||
|
|
||
|
This is straightforward to do and makes sense in the global context in which
|
||
|
we now program. However, I'm an American (= functionally monolingual), and so
|
||
|
not as expert on the various internationalisation issues as a European or
|
||
|
Asian would be -- so I'm punting it for now.
|
||
|
|
||
|
|
||
|
** Problems with traditional regexps
|
||
|
|
||
|
SRE notation was intended to fix a lot of things I didn't like about
|
||
|
working with traditional notation.
|
||
|
|
||
|
|
||
|
*** Traditional regexp notation doesn't scale over large regexps
|
||
|
|
||
|
Traditional regexp notation has a lot of problems, and the bigger
|
||
|
the regexp, the worse they get:
|
||
|
- They can't be laid out to express their structure with indentation.
|
||
|
- They can't be commented.
|
||
|
- They don't have an abstraction mechanism -- parts can't be named
|
||
|
and used, functions can't be defined. (This can be hacked using
|
||
|
mechanisms like sprintf(), but it is awkward and error prone.)
|
||
|
|
||
|
|
||
|
*** String constants
|
||
|
|
||
|
There's no need to backquote special characters in SRE string constants.
|
||
|
Just write them down in Scheme string-literal syntax.
|
||
|
|
||
|
|
||
|
*** Traditional regexp notation doesn't scale to rich operator sets
|
||
|
|
||
|
Traditional notation tries to use single characters as its operators:
|
||
|
. * ( ) ^ $ and so forth. Unfortunately, the more chars you reserve as
|
||
|
operators, the more backslash-quoting you have to do when you write down
|
||
|
constant strings. Eventually, you run out of special characters. Using a
|
||
|
single special character to prefix operators (as Gnu regexps do with
|
||
|
backslash) rapidly renders regexps unreadable -- especially when these
|
||
|
backslashes have to be doubled to get them into the host language's string
|
||
|
literals. When regexp packages such as Gnu, Perl, or Spencer start to expand
|
||
|
their operator repertoires, they are forced to adopt very unwieldy syntactic
|
||
|
mechanisms. For example, Spencer's notation for beginning-of-word and
|
||
|
end-of-word boundaries are [[:<:]] and [[:>:]], a somewhat bizarre bit of
|
||
|
syntactic jiggering.
|
||
|
|
||
|
S-expressions, on the other hand, are a little more verbose for
|
||
|
simple forms, but paying this cost up-front gets you into a general
|
||
|
framework that is extremely extensible. It's easy to add many new
|
||
|
operators to the SRE syntax -- as a result, SRE can be a very rich syntax.
|
||
|
You choose:
|
||
|
SRE: (w/nocase (word+ (~ ("aeiou"))))
|
||
|
Brand X: "[[:<:]]([b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z])+[[:>]]"
|
||
|
Note that not one of the three operators used in the SRE version is
|
||
|
available in traditional notation. That tells the story right there.
|
||
|
|
||
|
*** Traditional regexp [...] classes
|
||
|
|
||
|
There is a slew of special cases in the Posix grammar for [...] classes
|
||
|
to shoehorn [...]'s special chars (carat, right bracket, and hyphen) into the
|
||
|
notation as set elements. Examples:
|
||
|
- Right bracket terminates a char class... unless it's the first
|
||
|
character following the left bracket... or the second character,
|
||
|
following an initial ^.
|
||
|
- To put in a carat, place it at the *end* (well, perhaps *next* to the
|
||
|
end, if you also want to put in a hyphen) -- unless it's the only element,
|
||
|
then just punt the whole [...], and write it as a character.
|
||
|
- To put in a minus sign, really place it at the end, even after a carat.
|
||
|
Unless, that is, the whole char set is just the two characters carat
|
||
|
and hyphen, in which case you'd have [^-], which would mean something
|
||
|
else entirely -- so in this one case, flip the order of the two characters,
|
||
|
and put hyphen first: [-^].
|
||
|
- Be sure never to accidentally place a left-bracket element next
|
||
|
to an equals sign, colon, or period, because [=, [:, and [. are
|
||
|
collating element open-delimiters in Posix regexps -- you can
|
||
|
say things like "[ABC[:lower:]123]" to get A, B, C, 1, 2, 3 and
|
||
|
all the lower-case letters. Better shuffle the class elements around
|
||
|
to avoid these juxtapositions.
|
||
|
- There's no way, at all, to write the empty character class.
|
||
|
("[]" is not syntactically legal; if you try "[^\000-\177]", you will
|
||
|
probably blow up your C regexp engine with the non-terminating nul
|
||
|
byte, and, in any event, you are being ASCII specific.)
|
||
|
|
||
|
This is not even the whole set of special exceptions. You start putting
|
||
|
special characters into your [...] char classes, and you walk into a
|
||
|
mine field. Who designed this mess?
|
||
|
|
||
|
As evidence of this complexity, the code that translates general Scheme
|
||
|
character sets into this notation is the single largest and most complicated
|
||
|
part of the ADT->Posix-string compiler. Better, however, that the unparser code
|
||
|
puzzle out how to represent sets given all these ill-structured, error-prone
|
||
|
rules than for you to have to waste time thinking about it yourself, as
|
||
|
you must when you use traditional notation.
|
||
|
|
||
|
Even with its baroque syntactic rule set, the [...] construct is pretty
|
||
|
limited -- it lacks the compositional elements and general set operators
|
||
|
that SRE char classes have. Writing a [...] form that will match any
|
||
|
non-vowel letter is pretty painful because there is no set-difference
|
||
|
operator:
|
||
|
[b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z]
|
||
|
You'd have to stare at this for a minute to figure out what it is. The
|
||
|
corresponding SRE is much more transparent:
|
||
|
(- alpha ("aeiouAEIOU"))
|
||
|
|
||
|
|
||
|
*** ASCII nul and newline
|
||
|
|
||
|
Does . match a ASCII nul? Does it match a newline? Does [^x]? Unfortunately,
|
||
|
the same expressions have different meanings depending on the implementation
|
||
|
and the flags passed to the pattern-compiler functions. In SRE notation,
|
||
|
the behaviour of each element is unambiguously defined. No surprises; no
|
||
|
misunderstandings.
|
||
|
|
||
|
*** Newline
|
||
|
|
||
|
Various regexp systems can never seem to agree on the treatment of newline.
|
||
|
Is it matched by . or [^x]? Do the anchors ^ and $ match beginning/end of
|
||
|
line, or just beginning/end of string? Gnu regexps do it one way; Posix,
|
||
|
another. Posix provides a compile-time flag that shifts the meaning of
|
||
|
all these constructs from string-oriented to line-oriented -- but multiplexing
|
||
|
the notation in this global way means you can't do a bit of each in the
|
||
|
same regexp.
|
||
|
|
||
|
SRE's don't have this problem, partly because they aren't restricted to
|
||
|
just using punctuation characters such as . [ ] ^ $ for operators. It was
|
||
|
straightforward to add both bos/eos/any *and* bol/eol/nonl. Say what you
|
||
|
mean; mean what you say.
|
||
|
|
||
|
(Unfortunately, I don't have an underlying C engine that *implements*
|
||
|
both eos/bos and bol/eol matching. But I've got a driver, and that's a
|
||
|
start.)
|
||
|
|
||
|
|
||
|
*** Submatch and grouping
|
||
|
|
||
|
Parens are overloaded in Posix syntax as both grouping operators and
|
||
|
as submatch markers. Some implementations extend the traditional
|
||
|
syntax with awkward non-submatch-introducing grouping parentheses,
|
||
|
but they are non-standard extensions and syntactically awkward.
|
||
|
|
||
|
S-expressions, on the other hand, have no precedence issues, so
|
||
|
grouping is distinct from the submatch operator. Removing these
|
||
|
spurious submatches can have huge performance benefits, since submatch
|
||
|
assignment rules out non-backtracking DFA search (roughly speaking).
|
||
|
|
||
|
|
||
|
*** Grammar ambiguity
|
||
|
|
||
|
The traditional syntax has a lot of squirrelly cases in the grammar
|
||
|
to trip up the unwary. Some examples:
|
||
|
- Is "x+*" the regexp (* (+ "x")) or is it (: (+ "x") (* ""))?
|
||
|
- Is "" an empty choice (SRE (|), which never matches anything),
|
||
|
or an empty sequence (SRE (:), which always matches the empty string)?
|
||
|
|
||
|
SRE syntax doesn't have any of these problems.
|
||
|
|
||
|
|
||
|
*** Grammar limitations
|
||
|
|
||
|
As we've seen, there's no way to write an empty match in Posix notation,
|
||
|
i.e. something that will never match. [] is not an empty class, due to
|
||
|
bizarre special-casing of right-bracket in the special context of immediately
|
||
|
following a left bracket or left-bracket/carat.
|
||
|
|
||
|
This may not seem important, but the generality can be handy when the
|
||
|
regexp expressions are being computed, not hand-written (e.g., by a macro,
|
||
|
or when unparsing an ADT value).
|
||
|
|
||
|
Notations should provide coverage all the way out to the boundary cases.
|
||
|
When they fail to do this, someone, sooner or later, runs into trouble.
|
||
|
|
||
|
*** No exported ADT
|
||
|
|
||
|
It's hard to compute regexps with the string representation -- for example,
|
||
|
if you want to drop a constant string into a regexp, you have to write
|
||
|
a routine to quote the special chars. Code that wants to manipulate regexps
|
||
|
in terms of their structure is much harder to write in terms of the string
|
||
|
form than the structured ADT values. As one of Perlis' aphorisms states,
|
||
|
|
||
|
The string is a stark data structure and everywhere it is passed
|
||
|
there is much duplication of process. It is a perfect vehicle for
|
||
|
hiding information.
|
||
|
|
||
|
Working in terms of the grammar keeps you one step removed from the level at
|
||
|
which you want to be operating, and the grammar doesn't even permit you write
|
||
|
down straighforward boundary cases, such as the empty choice, the empty
|
||
|
sequence, or the empty char class.
|
||
|
|
||
|
|
||
|
*** Comments
|
||
|
|
||
|
You can comment SRE notation. The traditional notation doesn't permit this.
|
||
|
This is a big problem for large, complex regexps. (To be fair, Perl's
|
||
|
non-standard syntax *does* permit comments to be interleaved with pieces
|
||
|
of a regexp.)
|
||
|
|
||
|
|
||
|
*** Case sensitivity
|
||
|
|
||
|
Traditional notation has no support for case-sensitivity. While the Posix
|
||
|
pattern-compiler allows for a case-insensitive flag to be globally applied
|
||
|
to a whole pattern, this is distinct from the notation itself -- not the
|
||
|
right thing -- and does not provide for locality of scope.
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* Regexp functions
|
||
|
------------------
|
||
|
|
||
|
** Obsolete, deprecated procedures
|
||
|
==================================
|
||
|
|
||
|
These two procedures are survivors from the previous, now-obsolete scsh regexp
|
||
|
interface. Old code must open the re-old-funs package to access them. They
|
||
|
should not be used in new code.
|
||
|
|
||
|
(string-match posix-re-string string [start]) -> match or false
|
||
|
(make-regexp posix-re-string) -> re
|
||
|
Old functions for backwards compatibility. Will go away at some point in
|
||
|
the future.
|
||
|
|
||
|
** Standard procedures and syntax
|
||
|
=================================
|
||
|
|
||
|
(rx sre ...) Regexp macro
|
||
|
This allows you to describe a regexp value with SRE notation.
|
||
|
|
||
|
(regexp? x) -> bool
|
||
|
|
||
|
(regexp-search re str [start flags]) -> false or match-data
|
||
|
(regexp-search? re str [start flags]) -> boolean
|
||
|
FLAGS is bitwise-or of regexp/bos-not-bol and regexp/eos-not-eol.
|
||
|
regexp/bos-not-bol means the beginning of the string isn't a line-begin.
|
||
|
regexp/eos-not-eol is analogous. [They're currently ignored because
|
||
|
BOL and EOL aren't supported.]
|
||
|
|
||
|
Use REGEXP-SEARCH? when you don't need submatch information, as
|
||
|
it has the potential to be *significantly* faster on submatch-containing
|
||
|
regexps.
|
||
|
|
||
|
There is no longer a separate regexp "compilation" function; regexp
|
||
|
records are compiled for the C engine on demand, and the resulting
|
||
|
C structures are cached in the regexp structure after the first use.
|
||
|
|
||
|
(match:start m [i]) -> int or false
|
||
|
(match:end m [i]) -> int or false
|
||
|
(match:substring m [i]) -> string or false
|
||
|
|
||
|
(regexp-substitute port-or-false match-data . items)
|
||
|
An item is a string (copied verbatim), integer (match index),
|
||
|
'pre (chars before the match), or 'post (chars after the match).
|
||
|
#f for port means return a string.
|
||
|
|
||
|
See the scsh manual for more details.
|
||
|
|
||
|
(regexp-substitute/global port-or-false re str . items)
|
||
|
Same as above, except 'post item means recurse on post-match substring.
|
||
|
If RE doesn't match STR, returns STR.
|
||
|
|
||
|
(regexp-fold re kons knil s [finish start]) -> value
|
||
|
The following definition is a bit unwieldy, but the intuition is
|
||
|
simple: this procedure uses the regexp RE to divide up string S into
|
||
|
non-matching/matching chunks, and then "folds" the procedure KONS
|
||
|
across this sequence of chunks. It is useful when you wish to operate
|
||
|
on a string in sub-units defined by some regular expression, as are
|
||
|
the related REGEXP-FOLD-RIGHT and REGEXP-FOR-EACH procedures.
|
||
|
|
||
|
Search from START (defaulting to 0) for a match to RE; call
|
||
|
this match M. Let I be the index of the end of the match
|
||
|
(that is, (match:end M 0)). Loop as follows:
|
||
|
(regexp-fold re kons (kons START M knil) s finish I)
|
||
|
If there is no match, return instead
|
||
|
(finish START knil)
|
||
|
FINISH defaults to (lambda (i knil) knil)
|
||
|
|
||
|
In other words, we divide up S into a sequence of non-matching/matching
|
||
|
chunks:
|
||
|
NM1 M1 NM1 M2 ... NMk Mk NMlast
|
||
|
where NM1 is the initial part of S that isn't matched by the RE, M1 is the
|
||
|
first match, NM2 is the following part of S that isn't matched, M2 is the
|
||
|
second match, and so forth -- NMlast is the final non-matching chunk of
|
||
|
S. We apply KONS from left to right to build up a result, passing it one
|
||
|
non-matching/matching chunk each time: on an application (KONS i m KNIL),
|
||
|
the non-matching chunk goes from I to (match:begin m 0), and the following
|
||
|
matching chunk goes from (match:begin m 0) to (match:end m 0). The last
|
||
|
non-matching chunk NMlast is processed by FINISH. So the computation we
|
||
|
perform is
|
||
|
(final q (kons Jk MTCHk ... (kons J2 MTCH2 (kons J1 MTCH1 knil))...))
|
||
|
where Ji is the index of the start of NMi, MTCHi is a match value
|
||
|
describing Mi, and Q is the index of the beginning of NMlast.
|
||
|
|
||
|
Hint: The LET-MATCH macro is frequently useful for operating on the
|
||
|
match value M passed to the KONS function.
|
||
|
|
||
|
(regexp-fold-right re kons knil s [finish start]) -> value
|
||
|
This procedure repeatedly matches regexp RE across string S.
|
||
|
This divides S up into a sequence of matching/non-matching chunks:
|
||
|
NM0 M1 NM1 M2 NM2 ... Mk NMk
|
||
|
where NM0 is the initial part of S that isn't matched by the RE,
|
||
|
M1 is the first match, NM1 is the following part of S that isn't
|
||
|
matched, M2 is the second match, and so forth. We apply KONS from
|
||
|
right to left to build up a result
|
||
|
(final q (kons MTCH1 J1 (kons MTCH2 J2 ...(kons MTCHk JK knil)...)))
|
||
|
where MTCHi is a match value describing Mi, Ji is the index of the end of
|
||
|
NMi (or, equivalently, the beginning of Mi+1), and Q is the index of the
|
||
|
beginning of M1. In other words, KONS is passed a match, an index
|
||
|
describing the following non-matching text, and the value produced by
|
||
|
folding the following text. The FINAL function "polishes off" the fold
|
||
|
operation by handling the initial chunk of non-matching text (NM0, above).
|
||
|
FINISH defaults to (lambda (i knil) knil)
|
||
|
|
||
|
Example: To pick out all the matches to RE in S, say
|
||
|
(regexp-fold-right re
|
||
|
(lambda (m i lis) (cons (match:substring m 0) lis))
|
||
|
'() s)
|
||
|
|
||
|
Hint: The LET-MATCH macro is frequently useful for operating on the
|
||
|
match value M passed to the KONS function.
|
||
|
|
||
|
|
||
|
(regexp-for-each re proc s [start]) -> unspecific
|
||
|
Repeatedly match regexp RE against string S.
|
||
|
Apply PROC to each match that is produced.
|
||
|
Matches do not overlap.
|
||
|
|
||
|
Hint: The LET-MATCH macro is frequently useful for operating on the
|
||
|
match value M passed to the KONS function.
|
||
|
|
||
|
|
||
|
(let-match match-exp mvars body ...) Syntax
|
||
|
(if-match match-exp mvars on-match no-match) Syntax
|
||
|
MVARS is a list of vars that is bound to the match and submatches
|
||
|
of the string; #F is allowed as a don't-care element. For example,
|
||
|
(let-match (regexp-search date s)
|
||
|
(whole-date month day year)
|
||
|
...body...)
|
||
|
matches the regexp against string s, then evaluates the body of the
|
||
|
let-match in a scope where M is bound to the matched string, and
|
||
|
SM2 is bound to the string matched by the second submatch.
|
||
|
|
||
|
IF-MATCH is similar, but if the match expression is false,
|
||
|
then the no-match expression is evaluated; this would be an
|
||
|
error in LET-MATCH.
|
||
|
|
||
|
(match-cond (<match-exp> <match-vars> <body> ...) ; As in if-match
|
||
|
(test <exp> <body> ...) ; As in cond
|
||
|
(test <exp> => <proc>) ; As in cond
|
||
|
(else <body> ...)) ; As in cond
|
||
|
|
||
|
|
||
|
(flush-submatches re) -> re ; Returned value has no submatches
|
||
|
(uncase re) -> re ; Case-fold regexp
|
||
|
(simplify-regexp re) -> re ; Simplify the regexp
|
||
|
(uncase-char-set cset) -> re
|
||
|
(uncase-string str) -> re
|
||
|
|
||
|
|
||
|
(sre->regexp sre) -> re ; S-expression parser
|
||
|
(regexp->sre re) -> sre ; S-expression unparser
|
||
|
|
||
|
(posix-string->regexp string) -> re ; Posix regexp parser
|
||
|
(regexp->posix-string re) -> string ; Posix regexp unparser
|
||
|
- The string parser doesn't handle the exotica of character class
|
||
|
names such as [[:alnum:]]; I wrote in in three hours.
|
||
|
- The unparser produces Spencer-specific strings for bow/eow
|
||
|
elements; otherwise, it's Posix all the way.
|
||
|
|
||
|
You can use these tools to map between scsh regexps and Posix
|
||
|
regexp strings, which can be useful if you want to do conversion
|
||
|
between SRE's and Posix form. For example, you can write a particularly
|
||
|
complex regexp in SRE form, or compute it using the ADT constructors,
|
||
|
then convert to Posix form, print it out, cut and paste it into a
|
||
|
C or emacs lisp program. Or you can import an old regexp from some other
|
||
|
program, parse it into an ADT value, render it to an SRE, print it out,
|
||
|
then cut and paste it into a scsh program.
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* The scsh regexp ADT
|
||
|
---------------------
|
||
|
The following functions may be used to construct and examine scsh's
|
||
|
regexp abstract data type. They are in the following Scheme 48 packages:
|
||
|
re-adt-lib
|
||
|
re-lib
|
||
|
scsh
|
||
|
|
||
|
** Sequences
|
||
|
(re-seq? x) -> boolean ; Type predicate
|
||
|
(make-re-seq . re-list) -> re ; Basic constructor
|
||
|
(re-seq . re-list) -> re ; Smart constructor
|
||
|
(re-seq:elts re) -> re-list ; Accessors
|
||
|
(re-seq:tsm re) -> integer ; .
|
||
|
|
||
|
** Choices
|
||
|
(re-choice? x) -> boolean ; Type predicate
|
||
|
(make-re-choice re-list) -> re ; Basic constructor
|
||
|
(re-choice . re-list) -> re ; Smart constructor
|
||
|
(re-choice:elts . re) -> re-list ; Accessors
|
||
|
(re-choice:tsm re) -> integer ; .
|
||
|
|
||
|
** Repetition
|
||
|
(re-repeat? x) -> boolean ; Type predicate
|
||
|
(make-re-repeat from to body) ; Basic constructor
|
||
|
(re-repeat from to body) ; Smart constructor
|
||
|
(re-repeat:body re) -> re ; Accessors
|
||
|
(re-repeat:from re) -> integer ; .
|
||
|
(re-repeat:to re) -> integer ; .
|
||
|
(re-repeat:tsm re) -> integer ; .
|
||
|
|
||
|
** Submatches
|
||
|
(re-submatch? x) -> boolean ; Type predicate
|
||
|
(make-re-submatch body [pre-dsm post-dsm]) ; Basic constructor
|
||
|
(re-submatch body [pre-dsm post-dsm]) ; Smart constructor
|
||
|
(re-submatch:body re) -> re ; Accessors
|
||
|
(re-submatch:pre-dsm re) -> integer ; .
|
||
|
(re-submatch:post-dsm re) -> integer ; .
|
||
|
(re-submatch:tsm re) -> integer ; .
|
||
|
|
||
|
** String constants
|
||
|
(re-string? x) -> boolean ; Type predicate
|
||
|
(make-re-string chars) -> re ; Basic constructor
|
||
|
(re-string chars) -> re ; Basic constructor
|
||
|
(re-string:chars re) -> string ; Accessor
|
||
|
|
||
|
** Char sets
|
||
|
(re-char-set? x) -> boolean ; Type predicate
|
||
|
(make-re-char-set cset) -> re ; Basic constructor
|
||
|
(re-char-set cset) -> re ; Basic constructor
|
||
|
(re-char-set:cset re) -> char-set ; Accessor
|
||
|
|
||
|
** DSM
|
||
|
(re-dsm? x) -> boolean ; Type predicate
|
||
|
(make-re-dsm body pre-dsm post-dsm) -> re ; Basic constructor
|
||
|
(re-dsm body pre-dsm post-dsm) -> re ; Smart constructor
|
||
|
(re-dsm:body re) -> re ; Accessor
|
||
|
(re-dsm:pre-dsm re) -> integer ; .
|
||
|
(re-dsm:post-dsm re) -> integer ; .
|
||
|
(re-dsm:tsm re) -> integer ; .
|
||
|
|
||
|
** Primitive regexps
|
||
|
re-bos re-eos re-bol re-eol re-bow re-eow ; Primitive regexps
|
||
|
re-bos? re-eos? ; Type predicates
|
||
|
re-bol? re-eol?
|
||
|
re-bow? re-eow?
|
||
|
|
||
|
trivial-re trivial-re? ; ""
|
||
|
empty-re empty-re? ; (|)
|
||
|
re-any re-any? ; any
|
||
|
re-nonl ; (~ #\newline)
|
||
|
re-word ; word
|
||
|
These are non-primitive predefined regexps of general utility.
|
||
|
|
||
|
(regexp? x) -> boolean
|
||
|
(re-tsm re) -> integer
|
||
|
|
||
|
(clean-up-cres)
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* Syntax-hacking tools
|
||
|
----------------------
|
||
|
The Scheme 48 package rx-syntax-tools exports several tools for macro
|
||
|
hackers that want to use SREs in their macros. In the functions defined
|
||
|
below, COMPARE and RENAME parameters are as passed to Clinger-Rees
|
||
|
explicit-renaming low-level macros.
|
||
|
|
||
|
(if-sre-form form conseq-form alt-form) Syntax
|
||
|
If FORM is a legal SRE, this is equivalent to the expression
|
||
|
CONSEQ-FORM, otherwise it expands to ALT-FORM.
|
||
|
|
||
|
This is useful for high-level macro authors who want to write a macro
|
||
|
where one field in the macro can be an SRE or possibly something
|
||
|
else. E.g., we might have a conditional form wherein if the
|
||
|
test part of one arm is an SRE, it expands to a regexp match
|
||
|
on some implied value, otherwise the form is evaluated as a boolean
|
||
|
Scheme expression. Here's an example of how a high-level macro could
|
||
|
drop an expression bound to ?TEST-EXP into an IF-SRE-FORM:
|
||
|
|
||
|
(if-sre-form ?test-exp ; If ?TEST-EXP is a regexp,
|
||
|
(regexp-search? (rx ?test-exp) line) ; match it against the line,
|
||
|
?test-exp) ; otw it's a boolean exp.
|
||
|
|
||
|
(sre-form? form rename compare) -> boolean
|
||
|
This procedure is for low-level macros doing things equivalent to
|
||
|
IF-SRE-FORM. It returns true if the form is a legal SRE.
|
||
|
|
||
|
Note that neither of these tests does a deep recursion over the form
|
||
|
in the case where the form is a list. They simply check the car of the
|
||
|
form for one of the legal SRE keywords.
|
||
|
|
||
|
(parse-sre sre-form compare rename) -> re
|
||
|
(parse-sres sre-forms compare rename) -> re
|
||
|
Parse SRE-FORM into an ADT. Note that if the SRE is dynamic --
|
||
|
contains ,<exp> or ,@<exp> forms, or has repeat operators whose
|
||
|
from/to counts are not constants -- then the returned ADT will have
|
||
|
*Scheme expressions* in the corresponding slots of the regexp records
|
||
|
instead of the corresponding integer, char-set, or regexp. In other
|
||
|
words, we use the ADT as its own AST. It's called a "hack."
|
||
|
|
||
|
PARSE-SRES parses a list of SRE forms that comprise an implicit sequence.
|
||
|
|
||
|
(regexp->scheme re rename) -> Scheme-expression
|
||
|
Returns a Scheme expression that will construct the regexp RE
|
||
|
using ADT constructors such as make-re-sequence, make-re-repeat,
|
||
|
and so forth.
|
||
|
|
||
|
If the regexp is static, it will be simplified and pre-translated
|
||
|
to a Posix string as well, which will be part of the constructed
|
||
|
regexp value.
|
||
|
|
||
|
(static-regexp? re) -> boolean
|
||
|
Is the regexp a static one?
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
* Acknowledgements
|
||
|
------------------
|
||
|
If you want to know precise details on obscure features of Posix regexps,
|
||
|
and their associated algorithms, you have to ask Tom Lord or Henry Spencer.
|
||
|
I did.
|
||
|
|
||
|
Alan Bawden was the one who proposed making | a "polymorphic" char-class/regexp
|
||
|
union operator, with an inference pass to disambiguate, thus leaving the SRE
|
||
|
syntax open to full intersection/union/difference/complement extension. I
|
||
|
found this to be an amazingly clever idea. Alan also explained some esoteric
|
||
|
points concerning low-level macros to me.
|