Removed quicklisp

This commit is contained in:
Ian Keane 2020-02-25 06:20:57 -05:00
commit 96c0b3e15c
741 changed files with 42704 additions and 17 deletions

View file

@ -1,15 +1,6 @@
<<<<<<< HEAD
=======
#
# ~/.bash_profile # ~/.bash_profile
# #
# Get the aliases and functions # Get the aliases and functions
[ -f $HOME/.bashrc ] && . $HOME/.bashrc [ -f $HOME/.bashrc ] && . $HOME/.bashrc
[[ -f ~/.bashrc ]] && . ~/.bashrc [[ -f ~/.bashrc ]] && . ~/.bashrc
export XDG_CONFIG_HOME="$HOME/.config"
#export DISPLAY=:0
export BROWSER=firefox
export EDITOR=vim
export TERM=screen-256color
>>>>>>> 05f5a5326ea758311bda4cc4225cd0ce265b8ec0

View file

@ -1,6 +1,6 @@
db_file "~/.config/mpd/database" db_file "~/.config/mpd/database"
log_file "~/.config/mpd/log" log_file "~/.config/mpd/log"
music_directory "~/Music" music_directory "/mnt/media/music"
playlist_directory "~/.config/mpd/playlists" playlist_directory "~/.config/mpd/playlists"
pid_file "~/.config/mpd/pid" pid_file "~/.config/mpd/pid"
state_file "~/.config/mpd/state" state_file "~/.config/mpd/state"
@ -15,7 +15,7 @@ max_output_buffer_size "16384"
audio_output { audio_output {
type "alsa" type "alsa"
name "alsa for audio soundcard" name "alsa for audio soundcard"
mixer_type "software" # mixer_type "software"
} }
audio_output { audio_output {
@ -24,3 +24,17 @@ name "toggle_visualizer"
path "/tmp/mpd.fifo" path "/tmp/mpd.fifo"
format "44100:16:2" format "44100:16:2"
} }
audio_output {
type "pulse"
name "PulseAudio Output"
#server "localhost" # optional
#sink "alsa_output" # optional
}
audio_output {
type "alsa"
name "MPD"
device "pulse"
mixer_control "Master"
}

View file

@ -30,7 +30,7 @@ color listfocus_unread yellow default bold
color info red black bold color info red black bold
color article cyan default color article cyan default
browser linkhandler # browser linkhandler
macro , open-in-browser macro , open-in-browser
macro t set browser "tsp youtube-dl --add-metadata -ic"; open-in-browser ; set browser linkhandler macro t set browser "tsp youtube-dl --add-metadata -ic"; open-in-browser ; set browser linkhandler
macro a set browser "tsp youtube-dl --add-metadata -xic -f bestaudio/best"; open-in-browser ; set browser linkhandler macro a set browser "tsp youtube-dl --add-metadata -xic -f bestaudio/best"; open-in-browser ; set browser linkhandler

1
ranger/rc.conf Normal file
View file

@ -0,0 +1 @@
map bw shell wal -i %s

264
ranger/rifle.conf Normal file
View file

@ -0,0 +1,264 @@
# vim: ft=cfg
#
# This is the configuration file of "rifle", ranger's file executor/opener.
# Each line consists of conditions and a command. For each line the conditions
# are checked and if they are met, the respective command is run.
#
# Syntax:
# <condition1> , <condition2> , ... = command
#
# The command can contain these environment variables:
# $1-$9 | The n-th selected file
# $@ | All selected files
#
# If you use the special command "ask", rifle will ask you what program to run.
#
# Prefixing a condition with "!" will negate its result.
# These conditions are currently supported:
# match <regexp> | The regexp matches $1
# ext <regexp> | The regexp matches the extension of $1
# mime <regexp> | The regexp matches the mime type of $1
# name <regexp> | The regexp matches the basename of $1
# path <regexp> | The regexp matches the absolute path of $1
# has <program> | The program is installed (i.e. located in $PATH)
# env <variable> | The environment variable "variable" is non-empty
# file | $1 is a file
# directory | $1 is a directory
# number <n> | change the number of this command to n
# terminal | stdin, stderr and stdout are connected to a terminal
# X | $DISPLAY is not empty (i.e. Xorg runs)
#
# There are also pseudo-conditions which have a "side effect":
# flag <flags> | Change how the program is run. See below.
# label <label> | Assign a label or name to the command so it can
# | be started with :open_with <label> in ranger
# | or `rifle -p <label>` in the standalone executable.
# else | Always true.
#
# Flags are single characters which slightly transform the command:
# f | Fork the program, make it run in the background.
# | New command = setsid $command >& /dev/null &
# r | Execute the command with root permissions
# | New command = sudo $command
# t | Run the program in a new terminal. If $TERMCMD is not defined,
# | rifle will attempt to extract it from $TERM.
# | New command = $TERMCMD -e $command
# Note: The "New command" serves only as an illustration, the exact
# implementation may differ.
# Note: When using rifle in ranger, there is an additional flag "c" for
# only running the current file even if you have marked multiple files.
#-------------------------------------------
# Websites
#-------------------------------------------
# Rarely installed browsers get higher priority; It is assumed that if you
# install a rare browser, you probably use it. Firefox/konqueror/w3m on the
# other hand are often only installed as fallback browsers.
ext x?html?, has surf, X, flag f = surf -- file://"$1"
ext x?html?, has vimprobable, X, flag f = vimprobable -- "$@"
ext x?html?, has vimprobable2, X, flag f = vimprobable2 -- "$@"
ext x?html?, has qutebrowser, X, flag f = qutebrowser -- "$@"
ext x?html?, has dwb, X, flag f = dwb -- "$@"
ext x?html?, has jumanji, X, flag f = jumanji -- "$@"
ext x?html?, has luakit, X, flag f = luakit -- "$@"
ext x?html?, has uzbl, X, flag f = uzbl -- "$@"
ext x?html?, has uzbl-tabbed, X, flag f = uzbl-tabbed -- "$@"
ext x?html?, has uzbl-browser, X, flag f = uzbl-browser -- "$@"
ext x?html?, has uzbl-core, X, flag f = uzbl-core -- "$@"
ext x?html?, has midori, X, flag f = midori -- "$@"
ext x?html?, has chromium-browser, X, flag f = chromium-browser -- "$@"
ext x?html?, has chromium, X, flag f = chromium -- "$@"
ext x?html?, has google-chrome, X, flag f = google-chrome -- "$@"
ext x?html?, has opera, X, flag f = opera -- "$@"
ext x?html?, has firefox, X, flag f = firefox -- "$@"
ext x?html?, has seamonkey, X, flag f = seamonkey -- "$@"
ext x?html?, has iceweasel, X, flag f = iceweasel -- "$@"
ext x?html?, has epiphany, X, flag f = epiphany -- "$@"
ext x?html?, has konqueror, X, flag f = konqueror -- "$@"
ext x?html?, has elinks, terminal = elinks "$@"
ext x?html?, has links2, terminal = links2 "$@"
ext x?html?, has links, terminal = links "$@"
ext x?html?, has lynx, terminal = lynx -- "$@"
ext x?html?, has w3m, terminal = w3m "$@"
#-------------------------------------------
# Spreadsheets
#-------------------------------------------
ext csv = sc-im "$@"
ext xls = sc-im "$@"
ext xlsx = sc-im "$1"
#-------------------------------------------
# Misc
#-------------------------------------------
# Define the "editor" for text files as first action
mime ^text, label editor = ${VISUAL:-$EDITOR} -- "$@"
mime ^text, label pager = "$PAGER" -- "$@"
!mime ^text, label editor, ext xml|json|tex|py|pl|rb|js|sh|php = ${VISUAL:-$EDITOR} -- "$@"
!mime ^text, label pager, ext xml|json|tex|py|pl|rb|js|sh|php = "$PAGER" -- "$@"
ext 1 = man "$1"
ext s[wmf]c, has zsnes, X = zsnes "$1"
ext s[wmf]c, has snes9x-gtk,X = snes9x-gtk "$1"
ext nes, has fceux, X = fceux "$1"
ext exe = wine "$1"
name ^[mM]akefile$ = make
#--------------------------------------------
# Code
#-------------------------------------------
ext py = python -- "$1"
ext pl = perl -- "$1"
ext rb = ruby -- "$1"
ext js = node -- "$1"
ext sh = sh -- "$1"
ext php = php -- "$1"
#--------------------------------------------
# Audio without X
#-------------------------------------------
mime ^audio|ogg$, terminal, has mpv = mpv -- "$@"
mime ^audio|ogg$, terminal, has mplayer2 = mplayer2 -- "$@"
mime ^audio|ogg$, terminal, has mplayer = mplayer -- "$@"
ext midi?, terminal, has wildmidi = wildmidi -- "$@"
#--------------------------------------------
# Video/Audio with a GUI
#-------------------------------------------
mime ^video|audio, has gmplayer, X, flag f = gmplayer -- "$@"
mime ^video|audio, has smplayer, X, flag f = smplayer "$@"
mime ^video, has mpv, X, flag f = mpv -- "$@"
mime ^video, has mpv, X, flag f = mpv --fs -- "$@"
mime ^video, has mplayer2, X, flag f = mplayer2 -- "$@"
mime ^video, has mplayer2, X, flag f = mplayer2 -fs -- "$@"
mime ^video, has mplayer, X, flag f = mplayer -- "$@"
mime ^video, has mplayer, X, flag f = mplayer -fs -- "$@"
mime ^video|audio, has vlc, X, flag f = vlc -- "$@"
mime ^video|audio, has totem, X, flag f = totem -- "$@"
mime ^video|audio, has totem, X, flag f = totem --fullscreen -- "$@"
#--------------------------------------------
# Video without X:
#-------------------------------------------
mime ^video, terminal, !X, has mpv = mpv -- "$@"
mime ^video, terminal, !X, has mplayer2 = mplayer2 -- "$@"
mime ^video, terminal, !X, has mplayer = mplayer -- "$@"
#-------------------------------------------
# Documents
#-------------------------------------------
ext pdf, has llpp, X, flag f = llpp "$@"
ext pdf, has zathura, X, flag f = zathura -- "$@"
ext pdf, has mupdf, X, flag f = mupdf "$@"
ext pdf, has mupdf-x11,X, flag f = mupdf-x11 "$@"
ext pdf, has apvlv, X, flag f = apvlv -- "$@"
ext pdf, has xpdf, X, flag f = xpdf -- "$@"
ext pdf, has evince, X, flag f = evince -- "$@"
ext pdf, has atril, X, flag f = atril -- "$@"
ext pdf, has okular, X, flag f = okular -- "$@"
ext pdf, has epdfview, X, flag f = epdfview -- "$@"
ext pdf, has qpdfview, X, flag f = qpdfview "$@"
ext pdf, has open, X, flag f = open "$@"
ext docx?, has catdoc, terminal = catdoc -- "$@" | "$PAGER"
ext sxc|xlsx?|xlt|xlw|gnm|gnumeric, has gnumeric, X, flag f = gnumeric -- "$@"
ext sxc|xlsx?|xlt|xlw|gnm|gnumeric, has kspread, X, flag f = kspread -- "$@"
ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has libreoffice, X, flag f = libreoffice "$@"
ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has soffice, X, flag f = soffice "$@"
ext pptx?|od[dfgpst]|docx?|sxc|xlsx?|xlt|xlw|gnm|gnumeric, has ooffice, X, flag f = ooffice "$@"
ext djvu, has zathura,X, flag f = zathura -- "$@"
ext djvu, has evince, X, flag f = evince -- "$@"
ext djvu, has atril, X, flag f = atril -- "$@"
ext djvu, has djview, X, flag f = djview -- "$@"
ext epub, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
ext mobi, has ebook-viewer, X, flag f = ebook-viewer -- "$@"
#-------------------------------------------
# Image Viewing:
#-------------------------------------------
mime ^image/svg, has inkscape, X, flag f = inkscape -- "$@"
mime ^image/svg, has display, X, flag f = display -- "$@"
mime ^image, has pqiv, X, flag f = pqiv -- "$@"
mime ^image, has sxiv, X, flag f = sxiv -- "$@"
mime ^image, has feh, X, flag f = feh -- "$@"
mime ^image, has mirage, X, flag f = mirage -- "$@"
mime ^image, has ristretto, X, flag f = ristretto "$@"
mime ^image, has eog, X, flag f = eog -- "$@"
mime ^image, has eom, X, flag f = eom -- "$@"
mime ^image, has nomacs, X, flag f = nomacs -- "$@"
mime ^image, has geeqie, X, flag f = geeqie -- "$@"
mime ^image, has gwenview, X, flag f = gwenview -- "$@"
mime ^image, has gimp, X, flag f = gimp -- "$@"
ext xcf, X, flag f = gimp -- "$@"
#-------------------------------------------
# Archives
#-------------------------------------------
# avoid password prompt by providing empty password
ext 7z, has 7z = 7z -p l "$@" | "$PAGER"
# This requires atool
ext ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz, has atool = atool --list --each -- "$@" | "$PAGER"
ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has atool = atool --list --each -- "$@" | "$PAGER"
ext 7z|ace|ar|arc|bz2?|cab|cpio|cpt|deb|dgc|dmg|gz, has atool = atool --extract --each -- "$@"
ext iso|jar|msi|pkg|rar|shar|tar|tgz|xar|xpi|xz|zip, has atool = atool --extract --each -- "$@"
# Listing and extracting archives without atool:
ext tar|gz|bz2|xz, has tar = tar vvtf "$1" | "$PAGER"
ext tar|gz|bz2|xz, has tar = for file in "$@"; do tar vvxf "$file"; done
ext bz2, has bzip2 = for file in "$@"; do bzip2 -dk "$file"; done
ext zip, has unzip = unzip -l "$1" | less
ext zip, has unzip = for file in "$@"; do unzip -d "${file%.*}" "$file"; done
ext ace, has unace = unace l "$1" | less
ext ace, has unace = for file in "$@"; do unace e "$file"; done
ext rar, has unrar = unrar l "$1" | less
ext rar, has unrar = for file in "$@"; do unrar x "$file"; done
#-------------------------------------------
# Flag t fallback terminals
#-------------------------------------------
# Rarely installed terminal emulators get higher priority; It is assumed that
# if you install a rare terminal emulator, you probably use it.
# gnome-terminal/konsole/xterm on the other hand are often installed as part of
# a desktop environment or as fallback terminal emulators.
mime ^ranger/x-terminal-emulator, has terminology = terminology -e "$@"
mime ^ranger/x-terminal-emulator, has kitty = kitty -- "$@"
mime ^ranger/x-terminal-emulator, has alacritty = alacritty -e "$@"
mime ^ranger/x-terminal-emulator, has sakura = sakura -e "$@"
mime ^ranger/x-terminal-emulator, has lilyterm = lilyterm -e "$@"
#mime ^ranger/x-terminal-emulator, has cool-retro-term = cool-retro-term -e "$@"
mime ^ranger/x-terminal-emulator, has termite = termite -x '"$@"'
#mime ^ranger/x-terminal-emulator, has yakuake = yakuake -e "$@"
mime ^ranger/x-terminal-emulator, has guake = guake -ne "$@"
mime ^ranger/x-terminal-emulator, has tilda = tilda -c "$@"
mime ^ranger/x-terminal-emulator, has st = st -e "$@"
mime ^ranger/x-terminal-emulator, has terminator = terminator -x "$@"
mime ^ranger/x-terminal-emulator, has urxvt = urxvt -e "$@"
mime ^ranger/x-terminal-emulator, has pantheon-terminal = pantheon-terminal -e "$@"
mime ^ranger/x-terminal-emulator, has lxterminal = lxterminal -e "$@"
mime ^ranger/x-terminal-emulator, has mate-terminal = mate-terminal -x "$@"
mime ^ranger/x-terminal-emulator, has xfce4-terminal = xfce4-terminal -x "$@"
mime ^ranger/x-terminal-emulator, has konsole = konsole -e "$@"
mime ^ranger/x-terminal-emulator, has gnome-terminal = gnome-terminal -- "$@"
mime ^ranger/x-terminal-emulator, has xterm = xterm -e "$@"
#-------------------------------------------
# Misc
#-------------------------------------------
label wallpaper, number 11, mime ^image, has feh, X = feh --bg-scale "$1"
label wallpaper, number 12, mime ^image, has feh, X = feh --bg-tile "$1"
label wallpaper, number 13, mime ^image, has feh, X = feh --bg-center "$1"
label wallpaper, number 14, mime ^image, has feh, X = feh --bg-fill "$1"
# Define the editor for non-text files + pager as last action
!mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php = ask
label editor, !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php = ${VISUAL:-$EDITOR} -- "$@"
label pager, !mime ^text, !ext xml|json|csv|tex|py|pl|rb|js|sh|php = "$PAGER" -- "$@"
# The very last action, so that it's never triggered accidentally, is to execute a program:
mime application/x-executable = "$1"

View file

@ -0,0 +1 @@
dists/quicklisp/software/anaphora-20191007-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-ansi-text-20150804-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-colors-20180328-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-emb-20190521-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-ppcre-20190521-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-project-20190521-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/let-plus-20191130-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/local-time-20190710-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/prove-20171130-git/

View file

@ -0,0 +1 @@
dists/quicklisp/software/anaphora-20191007-git/anaphora.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-ansi-text-20150804-git/cl-ansi-text-test.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-ansi-text-20150804-git/cl-ansi-text.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-colors-20180328-git/cl-colors.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-emb-20190521-git/cl-emb.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/local-time-20190710-git/cl-postgres+local-time.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-ppcre-20190521-git/cl-ppcre-unicode.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-ppcre-20190521-git/cl-ppcre.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-project-20190521-git/cl-project-test.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/cl-project-20190521-git/cl-project.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/prove-20171130-git/cl-test-more.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/let-plus-20191130-git/let-plus.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/local-time-20190710-git/local-time.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/prove-20171130-git/prove-asdf.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/prove-20171130-git/prove-test.asd

View file

@ -0,0 +1 @@
dists/quicklisp/software/prove-20171130-git/prove.asd

View file

@ -0,0 +1,34 @@
language: lisp
sudo: required
env:
matrix:
- LISP=abcl
- LISP=allegro
- LISP=sbcl
- LISP=sbcl32
- LISP=ccl
- LISP=ccl32
- LISP=ecl
- LISP=clisp
- LISP=clisp32
- LISP=cmucl
matrix:
allow_failures:
# Disabled until issue #6 is fixed.
- env: LISP=clisp
- env: LISP=clisp32
# Disabled until cim supports cmucl.
- env: LISP=cmucl
install:
- curl -L https://github.com/tokenrove/cl-travis/raw/master/install.sh | sh
- if [ "${LISP:(-2)}" = "32" ]; then
sudo apt-get install -qq -y libc6-dev-i386;
fi
script:
- cl -e '(ql:quickload :anaphora/test)
(unless (asdf:oos :test-op :anaphora/test)
(uiop:quit 1))'

View file

@ -0,0 +1,3 @@
;;;; This file is part of the Anaphora package Common Lisp,
;;;; and has been placed in Public Domain by the author,
;;;; Nikodemus Siivola <nikodemus@random-state.net>

View file

@ -0,0 +1,50 @@
# Anaphora
Anaphora is the anaphoric macro collection from Hell: it includes many
new fiends in addition to old friends like `AIF` and `AWHEN`.
Anaphora has been placed in Public Domain by the author, [Nikodemus
Siivola](mailto:nikodemus@random-state.net).
# Installation
Use [quicklisp](http://www.quicklisp.org/), and simply:
```
CL-USER(1): (ql:quickload "anaphora")
```
# Documentation
Anaphoric macros provide implicit bindings for various
operations. Extensive use of anaphoric macros is not good style,
and probably makes you go blind as well — there's a reason why
Anaphora claims to be from Hell.
Anaphora provides two families of anaphoric macros, which can be
identified by their names and packages (both families are also
exported from the package `ANAPHORA`). The implicitly-bound symbol
`ANAPHORA:IT` is also exported from all three packages.
## Basic anaphora
#### Exported from package `ANAPHORA-BASIC`
These bind their first argument to `IT` via `LET`. In case of `COND`
all clauses have their test-values bound to `IT`.
Variants: `AAND`, `ALET`, `APROG1`, `AIF`, `ACOND`, `AWHEN`, `ACASE`,
`ACCASE`, `AECASE`, `ATYPECASE`, `ACTYPECASE`, and `AETYPECASE`.
## Symbol-macro anaphora
#### Exported from package `ANAPHORA-SYMBOL`
These bind their first argument (unevaluated) to `IT` via
SYMBOL-`MACROLET.`
Variants: `SOR`, `SLET`, `SIF`, `SCOND`, `SUNLESS`,
`SWHEN`, `SCASE`, `SCCASE`, `SECASE`, `STYPECASE`, `SCTYPECASE`,
`SETYPECASE`.
Also: `ASIF`, which binds via `LET` for the
then-clause, and `SYMBOL-MACROLET` for the else-clause.

View file

@ -0,0 +1,31 @@
;;;; -*- Mode: Lisp; Base: 10; Syntax: ANSI-Common-lisp; -*-
;;;; Anaphora: The Anaphoric Macro Package from Hell
;;;;
;;;; This been placed in Public Domain by the author,
;;;; Nikodemus Siivola <nikodemus@random-state.net>
(defsystem :anaphora
:version "0.9.6"
:description "The Anaphoric Macro Package from Hell"
:author "Nikodemus Siivola <nikodemus@random-state.net>"
:license "Public Domain"
:components
((:file "packages")
(:file "early" :depends-on ("packages"))
(:file "symbolic" :depends-on ("early"))
(:file "anaphora" :depends-on ("symbolic"))))
(defsystem :anaphora/test
:description "Tests for anaphora"
:author "Nikodemus Siivola <nikodemus@random-state.net>"
:license "Public Domain"
:depends-on (:anaphora :rt)
:components ((:file "tests")))
(defmethod perform ((o test-op) (c (eql (find-system :anaphora))))
(test-system :anaphora/test))
(defmethod perform ((o test-op) (c (eql (find-system :anaphora/test))))
(or (symbol-call :rt '#:do-tests)
(error "test-op failed")))

View file

@ -0,0 +1,162 @@
;;;; -*- Mode: Lisp; Base: 10; Syntax: ANSI-Common-Lisp; Package: ANAPHORA -*-
;;;; Anaphora: The Anaphoric Macro Package from Hell
;;;;
;;;; This been placed in Public Domain by the author,
;;;; Nikodemus Siivola <nikodemus@random-state.net>
(in-package :anaphora)
;;; This was the original implementation of SYMBOLIC -- and still good
;;; for getting the basic idea. Brian Masterbrooks solution to
;;; infinite recusion during macroexpansion, that nested forms of this
;;; are subject to, is in symbolic.lisp.
;;;
;;; (defmacro symbolic (op test &body body &environment env)
;;; `(symbol-macrolet ((it ,test))
;;; (,op it ,@body)))
(defmacro alet (form &body body)
"Binds the FORM to IT (via LET) in the scope of the BODY."
`(anaphoric ignore-first ,form (progn ,@body)))
(defmacro slet (form &body body)
"Binds the FORM to IT (via SYMBOL-MACROLET) in the scope of the BODY. IT can
be set with SETF."
`(symbolic ignore-first ,form (progn ,@body)))
(defmacro aand (first &rest rest)
"Like AND, except binds the first argument to IT (via LET) for the
scope of the rest of the arguments."
`(anaphoric and ,first ,@rest))
(defmacro sor (first &rest rest)
"Like OR, except binds the first argument to IT (via SYMBOL-MACROLET) for
the scope of the rest of the arguments. IT can be set with SETF."
`(symbolic or ,first ,@rest))
(defmacro aif (test then &optional else)
"Like IF, except binds the result of the test to IT (via LET) for
the scope of the then and else expressions."
`(anaphoric if ,test ,then ,else))
(defmacro sif (test then &optional else)
"Like IF, except binds the test form to IT (via SYMBOL-MACROLET) for
the scope of the then and else expressions. IT can be set with SETF"
`(symbolic if ,test ,then ,else))
(defmacro asif (test then &optional else)
"Like IF, except binds the result of the test to IT (via LET) for
the the scope of the then-expression, and the test form to IT (via
SYMBOL-MACROLET) for the scope of the else-expression. Within scope of
the else-expression, IT can be set with SETF."
`(let ((it ,test))
(if it
,then
(symbolic ignore-first ,test ,else))))
(defmacro aprog1 (first &body rest)
"Binds IT to the first form so that it can be used in the rest of the
forms. The whole thing returns IT."
`(anaphoric prog1 ,first ,@rest))
(defmacro awhen (test &body body)
"Like WHEN, except binds the result of the test to IT (via LET) for the scope
of the body."
`(anaphoric when ,test ,@body))
(defmacro swhen (test &body body)
"Like WHEN, except binds the test form to IT (via SYMBOL-MACROLET) for the
scope of the body. IT can be set with SETF."
`(symbolic when ,test ,@body))
(defmacro sunless (test &body body)
"Like UNLESS, except binds the test form to IT (via SYMBOL-MACROLET) for the
scope of the body. IT can be set with SETF."
`(symbolic unless ,test ,@body))
(defmacro acase (keyform &body cases)
"Like CASE, except binds the result of the keyform to IT (via LET) for the
scope of the cases."
`(anaphoric case ,keyform ,@cases))
(defmacro scase (keyform &body cases)
"Like CASE, except binds the keyform to IT (via SYMBOL-MACROLET) for the
scope of the body. IT can be set with SETF."
`(symbolic case ,keyform ,@cases))
(defmacro aecase (keyform &body cases)
"Like ECASE, except binds the result of the keyform to IT (via LET) for the
scope of the cases."
`(anaphoric ecase ,keyform ,@cases))
(defmacro secase (keyform &body cases)
"Like ECASE, except binds the keyform to IT (via SYMBOL-MACROLET) for the
scope of the cases. IT can be set with SETF."
`(symbolic ecase ,keyform ,@cases))
(defmacro accase (keyform &body cases)
"Like CCASE, except binds the result of the keyform to IT (via LET) for the
scope of the cases. Unlike CCASE, the keyform/place doesn't receive new values
possibly stored with STORE-VALUE restart; the new value is received by IT."
`(anaphoric ccase ,keyform ,@cases))
(defmacro sccase (keyform &body cases)
"Like CCASE, except binds the keyform to IT (via SYMBOL-MACROLET) for the
scope of the cases. IT can be set with SETF."
`(symbolic ccase ,keyform ,@cases))
(defmacro atypecase (keyform &body cases)
"Like TYPECASE, except binds the result of the keyform to IT (via LET) for
the scope of the cases."
`(anaphoric typecase ,keyform ,@cases))
(defmacro stypecase (keyform &body cases)
"Like TYPECASE, except binds the keyform to IT (via SYMBOL-MACROLET) for the
scope of the cases. IT can be set with SETF."
`(symbolic typecase ,keyform ,@cases))
(defmacro aetypecase (keyform &body cases)
"Like ETYPECASE, except binds the result of the keyform to IT (via LET) for
the scope of the cases."
`(anaphoric etypecase ,keyform ,@cases))
(defmacro setypecase (keyform &body cases)
"Like ETYPECASE, except binds the keyform to IT (via SYMBOL-MACROLET) for
the scope of the cases. IT can be set with SETF."
`(symbolic etypecase ,keyform ,@cases))
(defmacro actypecase (keyform &body cases)
"Like CTYPECASE, except binds the result of the keyform to IT (via LET) for
the scope of the cases. Unlike CTYPECASE, new values possible stored by the
STORE-VALUE restart are not received by the keyform/place, but by IT."
`(anaphoric ctypecase ,keyform ,@cases))
(defmacro sctypecase (keyform &body cases)
"Like CTYPECASE, except binds the keyform to IT (via SYMBOL-MACROLET) for
the scope of the cases. IT can be set with SETF."
`(symbolic ctypecase ,keyform ,@cases))
(defmacro acond (&body clauses)
"Like COND, except result of each test-form is bound to IT (via LET) for the
scope of the corresponding clause."
(labels ((rec (clauses)
(if clauses
(destructuring-bind ((test &body body) . rest) clauses
(if body
`(anaphoric if ,test (progn ,@body) ,(rec rest))
`(anaphoric if ,test it ,(rec rest))))
nil)))
(rec clauses)))
(defmacro scond (&body clauses)
"Like COND, except each test-form is bound to IT (via SYMBOL-MACROLET) for the
scope of the corresponsing clause. IT can be set with SETF."
(labels ((rec (clauses)
(if clauses
(destructuring-bind ((test &body body) . rest) clauses
(if body
`(symbolic if ,test (progn ,@body) ,(rec rest))
`(symbolic if ,test it ,(rec rest))))
nil)))
(rec clauses)))

View file

@ -0,0 +1,20 @@
;;;; -*- Mode: Lisp; Base: 10; Syntax: ANSI-Common-Lisp; Package: ANAPHORA -*-
;;;; Anaphora: The Anaphoric Macro Package from Hell
;;;;
;;;; This been placed in Public Domain by the author,
;;;; Nikodemus Siivola <nikodemus@random-state.net>
(in-package :anaphora)
(defmacro with-unique-names ((&rest bindings) &body body)
`(let ,(mapcar #'(lambda (binding)
(destructuring-bind (var prefix)
(if (consp binding) binding (list binding binding))
`(,var (gensym ,(string prefix)))))
bindings)
,@body))
(defmacro ignore-first (first expr)
(declare (ignore first))
expr)

View file

@ -0,0 +1,90 @@
;;;; -*- Mode: Lisp; Base: 10; Syntax: ANSI-Common-Lisp; Package: CL-USER -*-
;;;; Anaphora: The Anaphoric Macro Package from Hell
;;;;
;;;; This been placed in Public Domain by the author,
;;;; Nikodemus Siivola <nikodemus@random-state.net>
(defpackage :anaphora
(:use :cl)
(:export
#:it
#:alet
#:slet
#:aif
#:aand
#:sor
#:awhen
#:aprog1
#:acase
#:aecase
#:accase
#:atypecase
#:aetypecase
#:actypecase
#:acond
#:sif
#:asif
#:swhen
#:sunless
#:scase
#:secase
#:sccase
#:stypecase
#:setypecase
#:sctypecase
#:scond)
(:documentation
"ANAPHORA provides a full complement of anaphoric macros. Subsets of the
functionality provided by this package are exported from ANAPHORA-BASIC and
ANAPHORA-SYMBOL."))
(defpackage :anaphora-basic
(:use :cl :anaphora)
(:export
#:it
#:alet
#:aif
#:aand
#:awhen
#:aprog1
#:acase
#:aecase
#:accase
#:atypecase
#:aetypecase
#:actypecase
#:acond)
(:documentation
"ANAPHORA-BASIC provides all normal anaphoric constructs, which bind
primary values to IT."))
(defpackage :anaphora-symbol
(:use :cl :anaphora)
(:export
#:it
#:slet
#:sor
#:sif
#:asif
#:swhen
#:sunless
#:scase
#:secase
#:sccase
#:stypecase
#:setypecase
#:sctypecase
#:scond)
(:documentation
"ANAPHORA-SYMBOL provides ``symbolic anaphoric macros'', which bind forms
to IT via SYMBOL-MACROLET.
Examples:
(sor (gethash key table) (setf it default))
(asif (gethash key table)
(foo it) ; IT is a value bound by LET here
(setf it default)) ; IT is the GETHASH form bound by SYMBOL-MACROLET here
"))

View file

@ -0,0 +1,54 @@
;;;; -*- Mode: Lisp; Base: 10; Syntax: ANSI-Common-Lisp; Package: ANAPHORA -*-
;;;; Copyright (c) 2003 Brian Mastenbrook
;;;; Permission is hereby granted, free of charge, to any person obtaining
;;;; a copy of this software and associated documentation files (the
;;;; "Software"), to deal in the Software without restriction, including
;;;; without limitation the rights to use, copy, modify, merge, publish,
;;;; distribute, sublicense, and/or sell copies of the Software, and to
;;;; permit persons to whom the Software is furnished to do so, subject to
;;;; the following conditions:
;;;; The above copyright notice and this permission notice shall be
;;;; included in all copies or substantial portions of the Software.
;;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
;;;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
;;;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
;;;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
;;;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(in-package :anaphora)
(defmacro internal-symbol-macrolet (&rest whatever)
`(symbol-macrolet ,@whatever))
(define-setf-expander internal-symbol-macrolet (binding-forms place &environment env)
(multiple-value-bind (dummies vals newvals setter getter)
(get-setf-expansion place env)
(values dummies
(substitute `(symbol-macrolet ,binding-forms it) 'it vals)
newvals
`(symbol-macrolet ,binding-forms ,setter)
`(symbol-macrolet ,binding-forms ,getter))))
(with-unique-names (s-indicator current-s-indicator)
(defmacro symbolic (operation test &rest other-args)
(with-unique-names (this-s)
(let ((current-s (get s-indicator current-s-indicator)))
(setf (get s-indicator current-s-indicator) this-s)
`(symbol-macrolet
((,this-s (internal-symbol-macrolet ((it ,current-s)) ,test))
(it ,this-s))
(,operation it ,@other-args)))))
(defmacro anaphoric (op test &body body)
(with-unique-names (this-s)
(setf (get s-indicator current-s-indicator) this-s)
`(let* ((it ,test)
(,this-s it))
(declare (ignorable ,this-s))
(,op it ,@body)))))

View file

@ -0,0 +1,427 @@
;;;; Anaphora: The Anaphoric Macro Package from Hell
;;;;
;;;; This been placed in Public Domain by the author,
;;;; Nikodemus Siivola <nikodemus@random-state.net>
(defpackage :anaphora-test
(:use :cl :anaphora :rt))
(in-package :anaphora-test)
(deftest alet.1
(alet (1+ 1)
(1+ it))
3)
(deftest alet.2
(alet (1+ 1)
it
(1+ it))
3)
(deftest slet.1
(let ((x (list 1 2 3)))
(slet (car x)
(incf it) (values it x)))
2 (2 2 3))
(deftest aand.1
(aand (+ 1 1)
(+ 1 it))
3)
(deftest aand.2
(aand 1 t (values it 2))
1 2)
(deftest aand.3
(let ((x 1))
(aand (incf x) t t (values t it)))
t 2)
(deftest aand.4
(aand 1 (values t it))
t 1)
#+(or)
;;; bug or a feature? forms like this expand to
;;;
;;; (let ((it (values ...))) (and it ...))
;;;
(deftest aand.5
(aand (values nil t) it)
nil t)
(deftest sor.1
(let ((x (list nil)))
(sor (car x)
(setf it t))
x)
(t))
(deftest aif.1
(aif (+ 1 1)
(+ 1 it)
:never)
3)
(deftest aif.2
(let ((x 0))
(aif (incf x)
it
:never))
1)
(deftest aif.3
(let ((x 0))
(aif (eval `(and ,(incf x) nil))
:never
(list it x)))
(nil 1))
(deftest sif.1
(let ((x (list nil)))
(sif (car x)
(setf it :oops)
(setf it :yes!))
(car x))
:yes!)
(deftest sif.2
(let ((x (list t)))
(sif (car x)
(setf it :yes!)
(setf it :oops))
(car x))
:yes!)
(deftest sif.3
(sif (list 1 2 3)
(sif (car it)
(setf it 'a)
:foo))
a)
(deftest sif.4
(progn
(defclass sif.4 ()
((a :initform (list :sif))))
(with-slots (a)
(make-instance 'sif.4)
(sif a
(sif (car it)
it))))
:sif)
(deftest asif.1
(let ((x (list 0)))
(asif (incf (car x))
it
(list :oops it)))
1)
(deftest asif.2
(let ((x (list nil)))
(asif (car x)
(setf x :oops)
(setf it :yes!))
x)
(:yes!))
(deftest awhen.1
(let ((x 0))
(awhen (incf x)
(+ 1 it)))
2)
(deftest awhen.2
(let ((x 0))
(or (awhen (not (incf x))
t)
x))
1)
(deftest swhen.1
(let ((x 0))
(swhen x
(setf it :ok))
x)
:ok)
(deftest swhen.2
(let ((x nil))
(swhen x
(setf it :oops))
x)
nil)
(deftest sunless.1
(let ((x nil))
(sunless x
(setf it :ok))
x)
:ok)
(deftest sunless.2
(let ((x t))
(sunless x
(setf it :oops))
x)
t)
(deftest acase.1
(let ((x 0))
(acase (incf x)
(0 :no)
(1 (list :yes it))
(2 :nono)))
(:yes 1))
(deftest scase.1
(let ((x (list 3)))
(scase (car x)
(0 (setf it :no))
(3 (setf it :yes!))
(t (setf it :nono)))
x)
(:yes!))
(deftest aecase.1
(let ((x (list :x)))
(aecase (car x)
(:y :no)
(:x (list it :yes))))
(:x :yes))
(deftest aecase.2
(nth-value 0 (ignore-errors
(let ((x (list :x)))
(secase (car x)
(:y :no)))
:oops))
nil)
(deftest secase.1
(let ((x (list :x)))
(secase (car x)
(:y (setf it :no))
(:x (setf it :yes)))
x)
(:yes))
(deftest secase.2
(nth-value 0 (ignore-errors
(let ((x (list :x)))
(secase (car x)
(:y (setf it :no)))
:oops)))
nil)
(deftest accase.1
(let ((x (list :x)))
(accase (car x)
(:y :no)
(:x (list it :yes))))
(:x :yes))
(deftest accase.2
(let ((x (list :x)))
(handler-bind ((type-error (lambda (e) (store-value :z e))))
(accase (car x)
(:y (setf x :no))
(:z (setf x :yes))))
x)
:yes)
(deftest accase.3
(let ((x (list :x)))
(accase (car x)
(:x (setf it :foo)))
x)
(:x))
(deftest sccase.1
(let ((x (list :x)))
(sccase (car x)
(:y (setf it :no))
(:x (setf it :yes)))
x)
(:yes))
(deftest sccase.2
(let ((x (list :x)))
(handler-bind ((type-error (lambda (e) (store-value :z e))))
(sccase (car x)
(:y (setf it :no))
(:z (setf it :yes))))
x)
(:yes))
(deftest atypecase.1
(atypecase 1.0
(integer (+ 2 it))
(float (1- it)))
0.0)
(deftest atypecase.2
(atypecase "Foo"
(fixnum :no)
(hash-table :nono))
nil)
(deftest stypecase.1
(let ((x (list 'foo)))
(stypecase (car x)
(vector (setf it :no))
(symbol (setf it :yes)))
x)
(:yes))
(deftest stypecase.2
(let ((x (list :bar)))
(stypecase (car x)
(fixnum (setf it :no)))
x)
(:bar))
(deftest aetypecase.1
(aetypecase 1.0
(fixnum (* 2 it))
(float (+ 2.0 it))
(symbol :oops))
3.0)
(deftest aetypecase.2
(nth-value 0 (ignore-errors
(aetypecase 1.0
(symbol :oops))))
nil)
(deftest setypecase.1
(let ((x (list "Foo")))
(setypecase (car x)
(symbol (setf it :no))
(string (setf it "OK"))
(integer (setf it :noon)))
x)
("OK"))
(deftest setypecase.2
(nth-value 0 (ignore-errors
(setypecase 'foo
(string :nono))))
nil)
(deftest actypecase.1
(actypecase :foo
(string (list :string it))
(keyword (list :keyword it))
(symbol (list :symbol it)))
(:keyword :foo))
(deftest actypecase.2
(handler-bind ((type-error (lambda (e) (store-value "OK" e))))
(actypecase 0
(string it)))
"OK")
(deftest sctypecase.1
(let ((x (list 0)))
(sctypecase (car x)
(symbol (setf it 'symbol))
(bit (setf it 'bit)))
x)
(bit))
(deftest sctypecase.2
(handler-bind ((type-error (lambda (e) (store-value "OK" e))))
(let ((x (list 0)))
(sctypecase (car x)
(string (setf it :ok)))
x))
(:ok))
(deftest acond.1
(acond (:foo))
:foo)
(deftest acond.2
(acond ((null 1) (list :no it))
((+ 1 2) (list :yes it))
(t :nono))
(:yes 3))
(deftest acond.3
(acond ((= 1 2) :no)
(nil :nono)
(t :yes))
:yes)
;; Test COND with multiple forms in the implicit progn.
(deftest acond.4
(let ((foo))
(acond ((+ 2 2) (setf foo 38) (incf foo it) foo)
(t nil)))
42)
(deftest scond.1
(let ((x (list nil))
(y (list t)))
(scond ((car x) (setf it :nono))
((car y) (setf it :yes)))
(values x y))
(nil)
(:yes))
(deftest scond.2
(scond ((= 1 2) :no!))
nil)
(deftest aprog.1
(aprog1 :yes
(unless (eql it :yes) (error "Broken."))
:no)
:yes)
(deftest aif.sif.1
(sif 1 (aif it it))
1)
(deftest aif.sif.2
(aif 1 (sif it it))
1)
(deftest aif.sif.3
(aif (list 1 2 3)
(sif (car it)
(setf it 'a)
:foo))
a)
(deftest alet.slet.1
(slet 42 (alet 43 (slet it it)))
43)
(defun elt-like (index seq)
(elt seq index))
(define-setf-expander elt-like (index seq)
(let ((index-var (gensym "index"))
(seq-var (gensym "seq"))
(store (gensym "store")))
(values (list index-var seq-var)
(list index seq)
(list store)
`(if (listp ,seq-var)
(setf (nth ,index-var ,seq-var) ,store)
(setf (aref ,seq-var ,index-var) ,store))
`(if (listp ,seq-var)
(nth ,index-var ,seq-var)
(aref ,seq-var ,index-var)))))
(deftest symbolic.setf-expansion.1
(let ((cell (list nil)))
(sor (elt-like 0 cell) (setf it 1))
(equal cell '(1)))
t)

View file

@ -0,0 +1,13 @@
before_script:
- curl -O -L http://prdownloads.sourceforge.net/sbcl/sbcl-1.2.6-x86-64-linux-binary.tar.bz2
- tar xjf sbcl-1.2.6-x86-64-linux-binary.tar.bz2
- pushd sbcl-1.2.6-x86-64-linux/ && sudo bash install.sh && popd
- curl -O -L http://beta.quicklisp.org/quicklisp.lisp
- sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)' --eval '(quit)'
- curl -OL http://ccl.clozure.com/ftp/pub/release/1.10/ccl-1.10-linuxx86.tar.gz
- tar xzf ccl-1.10-linuxx86.tar.gz
- export PATH=`pwd`/ccl:$PATH
# - lx86cl64 -b --load quicklisp.lisp --eval '(progn (quicklisp-quickstart:install) (quit))'
script:
- ./ci-test-run.sh

View file

@ -0,0 +1,140 @@
# cl-ansi-text
Because color in your terminal is nice.
[![Build Status](https://travis-ci.org/pnathan/cl-ansi-text.svg?branch=master)](https://travis-ci.org/pnathan/cl-ansi-text)
## Usage example -
```lisp
* (ql:quickload :cl-ansi-text)
;To load "cl-ansi-text":
; Load 1 ASDF system:
; cl-ansi-text
;; Loading "cl-ansi-text"
; => (:CL-ANSI-TEXT)
```
The main macro is called `with-color`, which creates an enviroment where everything that is put on `stream` gets colored according to `color`. Color options are `:black`, `:red`, `:green`, `:yellow`, `:blue`, `:magenta`, `:cyan` and `:white`. You can also use a color structure from `CL-COLORS`, like `cl-colors:+red+`.
```lisp
* (import 'cl-ansi-text:with-color)
; => T
* (with-color (:red)
(princ "Gets printed red...")
(princ "and this too!"))
; Gets printed red...and this too!
; => "and this too!"
```
There are also functions with the name of the colors, that return the string, colored:
```lisp
* (import 'cl-ansi-text:yellow)
; => T
* (yellow "Yellow string")
; => "Yellow string"
* (princ (yellow "String with yellow background" :style :background))
; "String with yellow background"
; => "String with yellow background"
* (import 'cl-ansi-text:red)
; => T
* (princ
(concatenate
'string
(yellow "Five") " test results went " (red "terribly wrong") "!"))
; Five test results went terribly wrong!
; => "Five test results went terribly wrong!"
```
At any point, you can bind the `*enabled*` special variable to `nil`, and anything inside that binding will not be printed colorfully:
```lisp
* (let (cl-ansi-text:*enabled*)
(princ (red "This string is printed normally")))
```
# API
## BLUE
Returns a string with the `blue'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## MAGENTA
Returns a string with the `magenta'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## CYAN
Returns a string with the `cyan'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## GREEN
Returns a string with the `green'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## WITH-COLOR
Writes out the string denoting a switch to `color`, executes body,
then writes out the string denoting a `reset`.
*enabled* dynamically controls expansion..
## YELLOW
Returns a string with the `yellow'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## BLACK
Returns a string with the `black'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## *ENABLED*
Turns on/off the colorization of functions
## MAKE-COLOR-STRING
Takes either a cl-color or a list denoting the ANSI colors and
returns a string sufficient to change to the given color.
Will be dynamically controlled by *enabled* unless manually specified
otherwise
## RED
Returns a string with the `red'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## WHITE
Returns a string with the `white'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function.
## +RESET-COLOR-STRING+
This string will reset ANSI colors
# Note
Note that your terminal MUST be ANSI-compliant to show these
colors. My SLIME REPL (as of Feb 2013) does not display these
colors. I have to use a typical Linux/OSX terminal to see them.
This has been tested to work on a Linux system with SBCL, CLISP and
CCL. CCL may not work quite perfectly, some level of conniptions were
encountered in testing. The interested reader is advised to check the
MAKE-LOAD-FORM defmethod in cl-ansi-text.lisp.
An earlier variant was tested on OSX 10.6 with SBCL.
License: LLGPL

View file

@ -0,0 +1,15 @@
#!/bin/bash
error=0
if which sbcl; then
echo "CI run using SBCL"
sbcl --script run-tests.lisp
error=$?
fi
if which lx86cl64; then
echo "CI run using CCL"
lx86cl64 -b --load run-tests.lisp
error=$(($error+$?))
fi
exit $error

View file

@ -0,0 +1,11 @@
(asdf:defsystem #:cl-ansi-text-test
:depends-on ( #:cl-colors #:alexandria #:cl-ansi-text #:fiveam)
:components ((:module "test"
:components
((:file "cl-ansi-text-test"))))
:name "cl-ansi-text-test"
:version "1.0"
:maintainer "Paul Nathan"
:author "Paul Nathan"
:licence "LLGPL"
:description "Test system for cl-ansi-text")

View file

@ -0,0 +1,11 @@
(asdf:defsystem #:cl-ansi-text
:depends-on ( #:cl-colors #:alexandria)
:components ((:file "cl-ansi-text"))
:name "cl-ansi-text"
:version "1.0"
:maintainer "Paul Nathan"
:author "Paul Nathan"
:licence "LLGPL"
:description "ANSI control string characters, focused on color"
:long-description "ANSI control string management, specializing in
colors. Sometimes it is nice to have text output in colors")

View file

@ -0,0 +1,285 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Paul Nathan 2013
;;;; cl-ansi-text.lisp
;;;;
;;;; Portions of this code were written by taksatou under the
;;;; cl-rainbow name.
;;;;
;;;; A library to produce ANSI escape sequences. Particularly,
;;;; produces colorized text on terminals
(defpackage :cl-ansi-text
(:use :common-lisp)
(:export
#:with-color
#:make-color-string
#:+reset-color-string+
#:*enabled*
#:black
#:red
#:green
#:yellow
#:blue
#:magenta
#:cyan
#:white))
(in-package :cl-ansi-text)
;;; !!! NOTE TO CCL USERS !!!
;;;
;;; This seems to be *required* to make this compile in CCL. The
;;; reason is that CCL expects to be able to inline on compile, but
;;; structs don't set up that infrastructure by default.
;;;
;;; At least from the thread "Compiler problem, MCL 3.9" by Arthur
;;; Cater around '96.
#+ccl(common-lisp:eval-when (:compile-toplevel :load-toplevel :execute)
(defmethod make-load-form ((obj cl-colors:rgb ) &optional env)
(make-load-form-saving-slots obj)))
(defparameter *enabled* t
"Turns on/off the colorization of functions")
(defparameter +reset-color-string+
(concatenate 'string (list (code-char 27) #\[ #\0 #\m))
"This string will reset ANSI colors")
(defvar +cl-colors+
(vector
cl-colors:+black+
cl-colors:+red+
cl-colors:+green+
cl-colors:+yellow+
cl-colors:+blue+
cl-colors:+magenta+
cl-colors:+cyan+
cl-colors:+white+)
"CL-COLORS colors")
(eval-when (:compile-toplevel :load-toplevel :execute)
(defparameter +term-colors+
(vector
:black
:red
:green
:yellow
:blue
:magenta
:cyan
:white)
"Basic colors"))
(defparameter +text-style+
'((:foreground . 30)
(:background . 40))
"One or the other. Not an ANSI effect")
(defparameter +term-effects+
'((:unset . t)
(:reset . 0)
(:bright . 1)
(:italic . 3)
(:underline . 4)
(:blink . 5)
(:inverse . 7)
(:hide . 8)
(:normal . 22)
(:framed . 51)
(:encircled . 52)
(:overlined . 53)
(:not-framed-or-circled . 54)
(:not-overlined . 55))
"ANSI terminal effects")
(defun eq-colors (a b)
"Equality for cl-colors"
;; CL-COLORS LIB!
;; eql, equal doesn't quite work for compiled cl-colors on CCL
(and
(= (cl-colors:rgb-red a)
(cl-colors:rgb-red b))
(= (cl-colors:rgb-green a)
(cl-colors:rgb-green b))
(= (cl-colors:rgb-blue a)
(cl-colors:rgb-blue b))))
(defun cl-colors-to-ansi (color)
(position color +cl-colors+ :test #'eq-colors))
(defun term-colors-to-ansi (color)
(position color +term-colors+))
;; Find-X-code is the top-level interface for code-finding
(defun find-color-code (color)
"Find the list denoting the color"
(typecase color
;; Did we get a cl-color that we know about?
(cl-colors:rgb (cl-colors-to-ansi color))
(symbol (term-colors-to-ansi color))))
(defun find-effect-code (effect)
"Returns the number for the text effect OR
t if no effect should be used OR
nil if the effect is unknown.
effect should be a member of +term-effects+"
(cdr (assoc effect +term-effects+)))
(defun find-style-code (style)
(cdr (assoc style +text-style+)))
(defun rgb-code-p (color)
(typecase color
(list t)
(integer t)))
(defun generate-control-string (code)
"General ANSI code"
(format nil "~c[~a" (code-char #o33) code))
(defun generate-color-string (code)
;; m is the action character for color
(format nil "~am" (generate-control-string code)))
(defun build-control-string (color
&optional
(effect :unset)
(style :foreground))
"Color (cl-color or term-color)
Effect
Style"
(let ((effect-code (find-effect-code effect))
(color-code (find-color-code color))
(style-code (find-style-code style)))
;; Nil here indicates an error
(assert effect-code)
(assert style-code)
;; Returns a list for inspection; next layer turns it back into a
;; string.
(concatenate
'list
;; We split between RGB and 32-color here; this preserves the
;; interface without cluttering the 32-color code up.
;;
(let ((codes nil))
(unless (eq effect-code t)
(setf codes (cons effect-code codes)))
(if (rgb-code-p color)
(setf codes (cons (rgb-color-code color style) codes))
(setf codes (cons (+ style-code color-code) codes)))
(generate-color-string (format nil "~{~A~^;~}" codes))))))
;; Public callables.
(defun make-color-string (color &key
(effect :unset)
(style :foreground)
((enabled *enabled*) *enabled*))
"Takes either a cl-color or a list denoting the ANSI colors and
returns a string sufficient to change to the given color.
Will be dynamically controlled by *enabled* unless manually specified
otherwise"
(when *enabled*
(concatenate 'string
(build-control-string color effect style))))
(defmacro with-color ((color &key
(stream t)
(effect :unset)
(style :foreground))
&body body)
"Writes out the string denoting a switch to `color`, executes body,
then writes out the string denoting a `reset`.
*enabled* dynamically controls expansion.."
`(progn
(when *enabled*
(format ,stream "~a" (make-color-string ,color
:effect ,effect
:style ,style)))
(unwind-protect
(progn
,@body)
(when *enabled*
(format ,stream "~a" +reset-color-string+)))))
(defmacro gen-color-functions (color-names-vector)
`(progn
,@(map 'list
(lambda (color)
`(defun ,(intern (symbol-name color)) (string &key
(effect :unset)
(style :foreground))
,(concatenate
'string
"Returns a string with the `" (string-downcase color)
"'string denotation preppended and the `reset' string denotation appended.
*enabled* dynamically controls the function." )
(concatenate
'string
(when *enabled*
(format nil "~a" (make-color-string ,color
:effect effect
:style style)))
string
(when *enabled*
(format nil "~a" +reset-color-string+)))))
color-names-vector)))
(gen-color-functions #.(coerce +term-colors+ 'list))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; RGB color codes for some enhanced terminals
;;; http://www.frexx.de/xterm-256-notes/
(defun rgb-to-ansi (red green blue)
(let ((ansi-domain (mapcar #'(lambda (x)
(floor (* 6 (/ x 256.0))))
(list red green blue))))
(+ 16
(* 36 (first ansi-domain))
(* 6 (second ansi-domain))
(third ansi-domain))))
(defun code-from-rgb (style red green blue)
(format nil "~d;5;~d"
(if (eql style :foreground) 38 48)
(rgb-to-ansi red green blue)))
(defgeneric rgb-color-code (color &optional style)
(:documentation
"Returns the 256-color code suitable for rendering on the Linux
extensions to xterm"))
(defmethod rgb-color-code ((color list) &optional (style :foreground))
(unless (consp color)
(error "~a must be a three-integer list" color))
(unless (and (integerp (first color))
(integerp (second color))
(integerp (second color)))
(error "~a must have three integers" color))
(code-from-rgb style
(first color)
(second color)
(third color)))
(defmethod rgb-color-code ((color integer) &optional (style :foreground))
;; Takes RGB integer ala Web integers
(code-from-rgb style
;; classic bitmask
(ash (logand color #xff0000) -16)
(ash (logand color #x00ff00) -8)
(logand color #x0000ff)))

View file

@ -0,0 +1,29 @@
#-quicklisp
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
(user-homedir-pathname))))
(when (probe-file quicklisp-init)
(load quicklisp-init)))
#+sbcl(require "sb-posix")
(defparameter *pwd*
(concatenate 'string
(progn #+sbcl(sb-posix:getcwd)
#+ccl(ccl::current-directory-name))
"/"))
(push *pwd* asdf:*central-registry*)
(ql:quickload '(:cl-colors
:alexandria
:fiveam
:cl-ansi-text
:cl-ansi-text-test))
(let ((result-status (cl-ansi-text-test::ci-run)))
(let ((posix-status
(if result-status 0 1)))
#+sbcl(sb-posix:exit posix-status)
#+ccl (quit posix-status)))

View file

@ -0,0 +1,123 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; test suite for cl-ansi-text
(defpackage :cl-ansi-text-test
(:use :common-lisp
:cl-user
:cl-ansi-text
:fiveam))
(in-package :cl-ansi-text-test)
(use-package :fiveam)
(use-package :cl-ansi-text)
(def-suite test-suite
:description "test suite.")
(in-suite test-suite)
(test basic-color-strings
"Test the basic stuff"
(is (equal '(#\Esc #\[ #\3 #\1 #\m)
(cl-ansi-text::build-control-string :red :unset :foreground)))
(is (equal '(#\Esc #\[ #\4 #\1 #\m)
(cl-ansi-text::build-control-string :red :unset :background)))
(is (equal '(#\Esc #\[ #\4 #\2 #\; #\1 #\m)
(cl-ansi-text::build-control-string :green :bright :background))))
(test enabled-connectivity
"Test *enabled*'s capability"
(is (equal '(#\Esc #\[ #\3 #\1 #\m)
(let ((*enabled* t))
(concatenate
'list
(cl-ansi-text:make-color-string :red)))))
(is (equal '()
(let ((*enabled* nil))
(concatenate
'list
(cl-ansi-text:make-color-string :red)))))
(is (equal "hi"
(let ((*enabled* nil))
(with-output-to-string (s)
(with-color (:red :stream s) (format s "hi"))))))
(is (equal '(#\Esc #\[ #\3 #\1 #\m #\T #\e #\s #\t #\! #\Esc #\[ #\0 #\m)
(concatenate
'list
(with-output-to-string (s)
(with-color (:red :stream s)
(format s "Test!")))))))
(test rgb-suite
"Test RGB colors"
(is (equal '(#\Esc #\[ #\3 #\8 #\; #\5 #\; #\2 #\1 #\4 #\m)
(cl-ansi-text::build-control-string #xFFAA00
:unset :foreground)))
(is (equal '(#\Esc #\[ #\4 #\8 #\; #\5 #\; #\2 #\1 #\4 #\m)
(cl-ansi-text::build-control-string #xFFAA00
:unset :background)))
(is (equal '(#\Esc #\[ #\4 #\8 #\; #\5 #\; #\1 #\6 #\m)
(cl-ansi-text::build-control-string #x000000
:unset :background)))
(is (equal '(#\Esc #\[ #\4 #\8 #\; #\5 #\; #\2 #\3 #\1 #\m)
(cl-ansi-text::build-control-string #xFFFFFF
:unset :background))))
(test color-named-functions
(let ((str "Test string."))
(is (equal (black str)
(with-output-to-string (s)
(with-color (:black :stream s)
(format s str)))))
(is (equal (red str)
(with-output-to-string (s)
(with-color (:red :stream s)
(format s str)))))
(is (equal (green str)
(with-output-to-string (s)
(with-color (:green :stream s)
(format s str)))))
(is (equal (yellow str)
(with-output-to-string (s)
(with-color (:yellow :stream s)
(format s str)))))
(is (equal (blue str)
(with-output-to-string (s)
(with-color (:blue :stream s)
(format s str)))))
(is (equal (magenta str)
(with-output-to-string (s)
(with-color (:magenta :stream s)
(format s str)))))
(is (equal (cyan str)
(with-output-to-string (s)
(with-color (:cyan :stream s)
(format s str)))))
(is (equal (white str)
(with-output-to-string (s)
(with-color (:white :stream s)
(format s str)))))))
(test color-named-functions-*enabled*
(let ((str "Other test string.")
(*enabled* nil))
(is
(equal str
(white (cyan (magenta (blue (yellow (green (red (black str))))))))))))
(defun run-tests ()
(let ((results (run 'test-suite)))
(explain! results)
(if (position-if #'(lambda (e)
(eq (type-of e)
'IT.BESE.FIVEAM::TEST-FAILURE
))
results)
nil
t)))
(defun ci-run ()
(run-tests))

View file

@ -0,0 +1,8 @@
## note: this works on my system, you don't need to run it because the
## distribution contains the generated file
SBCL=/usr/bin/sbcl
colornames.lisp: /usr/share/X11/rgb.txt parse-x11-colors.lisp
rm -f colornames.lisp
$(SBCL) --load parse-x11-colors.lisp --eval '(quit)'

View file

@ -0,0 +1,43 @@
*IMPORTANT* This library is [[https://tpapp.github.io/post/orphaned-lisp-libraries/][unsupported]].
* cl-colors: a simple color library for Common Lisp
This is a very simple color library for Common Lisp, providing
1. Types for representing colors in HSV and RGB spaces.
2. Simple conversion functions between the above types (and also hexadecimal representation for RGB).
3. Some predefined colors (currently X11 color names -- of course the library does not depend on X11).
** Examples
#+BEGIN_SRC lisp
(let ((color1 (hsv 107 62/100 52/100)) ; greenish
(color2 (rgb 14/15 26/51 14/15)) ; = violet from X11
(color3 (as-rgb "ff9e00"))) ; from hexadecimal
(list ;
(as-rgb color1) ; converting to RGB
(rgb-combination color1 +blue+ 0.4) ; HSV autoconverted to RGB
(hsv-combination color2 +blue+ 0.4) ; RGB autoconverted to HSV
color3))
#+END_SRC
evaluates to
#+BEGIN_EXAMPLE
'(#S(RGB :RED 20059/75000 :GREEN 13/25 :BLUE 247/1250)
#S(RGB :RED 0.160472 :GREEN 0.312 :BLUE 0.51856) ; observe float contagion
#S(HSV :HUE 60.0 :SATURATION 0.6722689 :VALUE 0.96000004)
#S(RGB :RED 1 :GREEN 158/255 :BLUE 0))
#+END_EXAMPLE
Observe the float contagion: =cl-colors= functions don't care about the type of the numbers as long as they are a subtype of =real= and within the right range.
** Documentation
This library is so simple that it does not need a lot of documentation --- just look at the docsstrings in =colors.lisp=.
** Regeneration of the X11 color names
Normally you should not need to do this, the sources already contain the autogenerated file =colornames.lisp=. However, if for some reason you need to regenerate this, you can use =make=. Even though the library itself does not depend on X11, regenerating this file will require the appropriate file in X11.
** Bugs and issues
Please report them on [[https://github.com/tpapp/cl-colors/issues][Github]].

View file

@ -0,0 +1,19 @@
(defsystem #:cl-colors
:description "Simple color library for Common Lisp"
:version "0.2"
:author "Tamas K Papp <tkpapp@gmail.com>"
:license "Boost Software License - Version 1.0"
:serial t
:components ((:file "package")
(:file "colors")
(:file "colornames")
(:file "hexcolors"))
:depends-on (#:alexandria #:let-plus))
(defsystem #:cl-colors-tests
:description "Unit tests for CL-COLORS."
:author "Tamas K Papp <tkpapp@gmail.com>"
:license "Boost Software License - Version 1.0"
:serial t
:components ((:file "test"))
:depends-on (#:cl-colors #:lift))

View file

@ -0,0 +1,663 @@
;;;; This file was generated automatically by parse-x11.lisp
;;;; Please do not edit directly, just run make if necessary (but should not be).
(in-package #:cl-colors)
(define-rgb-color snow 1 50/51 50/51)
(define-rgb-color ghostwhite 248/255 248/255 1)
(define-rgb-color whitesmoke 49/51 49/51 49/51)
(define-rgb-color gainsboro 44/51 44/51 44/51)
(define-rgb-color floralwhite 1 50/51 16/17)
(define-rgb-color oldlace 253/255 49/51 46/51)
(define-rgb-color linen 50/51 16/17 46/51)
(define-rgb-color antiquewhite 50/51 47/51 43/51)
(define-rgb-color papayawhip 1 239/255 71/85)
(define-rgb-color blanchedalmond 1 47/51 41/51)
(define-rgb-color bisque 1 76/85 196/255)
(define-rgb-color peachpuff 1 218/255 37/51)
(define-rgb-color navajowhite 1 74/85 173/255)
(define-rgb-color moccasin 1 76/85 181/255)
(define-rgb-color cornsilk 1 248/255 44/51)
(define-rgb-color ivory 1 1 16/17)
(define-rgb-color lemonchiffon 1 50/51 41/51)
(define-rgb-color seashell 1 49/51 14/15)
(define-rgb-color honeydew 16/17 1 16/17)
(define-rgb-color mintcream 49/51 1 50/51)
(define-rgb-color azure 16/17 1 1)
(define-rgb-color aliceblue 16/17 248/255 1)
(define-rgb-color lavender 46/51 46/51 50/51)
(define-rgb-color lavenderblush 1 16/17 49/51)
(define-rgb-color mistyrose 1 76/85 15/17)
(define-rgb-color white 1 1 1)
(define-rgb-color black 0 0 0)
(define-rgb-color darkslategray 47/255 79/255 79/255)
(define-rgb-color darkslategrey 47/255 79/255 79/255)
(define-rgb-color dimgray 7/17 7/17 7/17)
(define-rgb-color dimgrey 7/17 7/17 7/17)
(define-rgb-color slategray 112/255 128/255 48/85)
(define-rgb-color slategrey 112/255 128/255 48/85)
(define-rgb-color lightslategray 7/15 8/15 3/5)
(define-rgb-color lightslategrey 7/15 8/15 3/5)
(define-rgb-color gray 38/51 38/51 38/51)
(define-rgb-color grey 38/51 38/51 38/51)
(define-rgb-color lightgrey 211/255 211/255 211/255)
(define-rgb-color lightgray 211/255 211/255 211/255)
(define-rgb-color midnightblue 5/51 5/51 112/255)
(define-rgb-color navy 0 0 128/255)
(define-rgb-color navyblue 0 0 128/255)
(define-rgb-color cornflowerblue 20/51 149/255 79/85)
(define-rgb-color darkslateblue 24/85 61/255 139/255)
(define-rgb-color slateblue 106/255 6/17 41/51)
(define-rgb-color mediumslateblue 41/85 104/255 14/15)
(define-rgb-color lightslateblue 44/85 112/255 1)
(define-rgb-color mediumblue 0 0 41/51)
(define-rgb-color royalblue 13/51 7/17 15/17)
(define-rgb-color blue 0 0 1)
(define-rgb-color dodgerblue 2/17 48/85 1)
(define-rgb-color deepskyblue 0 191/255 1)
(define-rgb-color skyblue 9/17 206/255 47/51)
(define-rgb-color lightskyblue 9/17 206/255 50/51)
(define-rgb-color steelblue 14/51 26/51 12/17)
(define-rgb-color lightsteelblue 176/255 196/255 74/85)
(define-rgb-color lightblue 173/255 72/85 46/51)
(define-rgb-color powderblue 176/255 224/255 46/51)
(define-rgb-color paleturquoise 35/51 14/15 14/15)
(define-rgb-color darkturquoise 0 206/255 209/255)
(define-rgb-color mediumturquoise 24/85 209/255 4/5)
(define-rgb-color turquoise 64/255 224/255 208/255)
(define-rgb-color cyan 0 1 1)
(define-rgb-color lightcyan 224/255 1 1)
(define-rgb-color cadetblue 19/51 158/255 32/51)
(define-rgb-color mediumaquamarine 2/5 41/51 2/3)
(define-rgb-color aquamarine 127/255 1 212/255)
(define-rgb-color darkgreen 0 20/51 0)
(define-rgb-color darkolivegreen 1/3 107/255 47/255)
(define-rgb-color darkseagreen 143/255 188/255 143/255)
(define-rgb-color seagreen 46/255 139/255 29/85)
(define-rgb-color mediumseagreen 4/17 179/255 113/255)
(define-rgb-color lightseagreen 32/255 178/255 2/3)
(define-rgb-color palegreen 152/255 251/255 152/255)
(define-rgb-color springgreen 0 1 127/255)
(define-rgb-color lawngreen 124/255 84/85 0)
(define-rgb-color green 0 1 0)
(define-rgb-color chartreuse 127/255 1 0)
(define-rgb-color mediumspringgreen 0 50/51 154/255)
(define-rgb-color greenyellow 173/255 1 47/255)
(define-rgb-color limegreen 10/51 41/51 10/51)
(define-rgb-color yellowgreen 154/255 41/51 10/51)
(define-rgb-color forestgreen 2/15 139/255 2/15)
(define-rgb-color olivedrab 107/255 142/255 7/51)
(define-rgb-color darkkhaki 63/85 61/85 107/255)
(define-rgb-color khaki 16/17 46/51 28/51)
(define-rgb-color palegoldenrod 14/15 232/255 2/3)
(define-rgb-color lightgoldenrodyellow 50/51 50/51 14/17)
(define-rgb-color lightyellow 1 1 224/255)
(define-rgb-color yellow 1 1 0)
(define-rgb-color gold 1 43/51 0)
(define-rgb-color lightgoldenrod 14/15 13/15 26/51)
(define-rgb-color goldenrod 218/255 11/17 32/255)
(define-rgb-color darkgoldenrod 184/255 134/255 11/255)
(define-rgb-color rosybrown 188/255 143/255 143/255)
(define-rgb-color indianred 41/51 92/255 92/255)
(define-rgb-color saddlebrown 139/255 23/85 19/255)
(define-rgb-color sienna 32/51 82/255 3/17)
(define-rgb-color peru 41/51 133/255 21/85)
(define-rgb-color burlywood 74/85 184/255 9/17)
(define-rgb-color beige 49/51 49/51 44/51)
(define-rgb-color wheat 49/51 74/85 179/255)
(define-rgb-color sandybrown 244/255 164/255 32/85)
(define-rgb-color tan 14/17 12/17 28/51)
(define-rgb-color chocolate 14/17 7/17 2/17)
(define-rgb-color firebrick 178/255 2/15 2/15)
(define-rgb-color brown 11/17 14/85 14/85)
(define-rgb-color darksalmon 233/255 10/17 122/255)
(define-rgb-color salmon 50/51 128/255 38/85)
(define-rgb-color lightsalmon 1 32/51 122/255)
(define-rgb-color orange 1 11/17 0)
(define-rgb-color darkorange 1 28/51 0)
(define-rgb-color coral 1 127/255 16/51)
(define-rgb-color lightcoral 16/17 128/255 128/255)
(define-rgb-color tomato 1 33/85 71/255)
(define-rgb-color orangered 1 23/85 0)
(define-rgb-color red 1 0 0)
(define-rgb-color hotpink 1 7/17 12/17)
(define-rgb-color deeppink 1 4/51 49/85)
(define-rgb-color pink 1 64/85 203/255)
(define-rgb-color lightpink 1 182/255 193/255)
(define-rgb-color palevioletred 73/85 112/255 49/85)
(define-rgb-color maroon 176/255 16/85 32/85)
(define-rgb-color mediumvioletred 199/255 7/85 133/255)
(define-rgb-color violetred 208/255 32/255 48/85)
(define-rgb-color magenta 1 0 1)
(define-rgb-color violet 14/15 26/51 14/15)
(define-rgb-color plum 13/15 32/51 13/15)
(define-rgb-color orchid 218/255 112/255 214/255)
(define-rgb-color mediumorchid 62/85 1/3 211/255)
(define-rgb-color darkorchid 3/5 10/51 4/5)
(define-rgb-color darkviolet 148/255 0 211/255)
(define-rgb-color blueviolet 46/85 43/255 226/255)
(define-rgb-color purple 32/51 32/255 16/17)
(define-rgb-color mediumpurple 49/85 112/255 73/85)
(define-rgb-color thistle 72/85 191/255 72/85)
(define-rgb-color snow1 1 50/51 50/51)
(define-rgb-color snow2 14/15 233/255 233/255)
(define-rgb-color snow3 41/51 67/85 67/85)
(define-rgb-color snow4 139/255 137/255 137/255)
(define-rgb-color seashell1 1 49/51 14/15)
(define-rgb-color seashell2 14/15 229/255 74/85)
(define-rgb-color seashell3 41/51 197/255 191/255)
(define-rgb-color seashell4 139/255 134/255 26/51)
(define-rgb-color antiquewhite1 1 239/255 73/85)
(define-rgb-color antiquewhite2 14/15 223/255 4/5)
(define-rgb-color antiquewhite3 41/51 64/85 176/255)
(define-rgb-color antiquewhite4 139/255 131/255 8/17)
(define-rgb-color bisque1 1 76/85 196/255)
(define-rgb-color bisque2 14/15 71/85 61/85)
(define-rgb-color bisque3 41/51 61/85 158/255)
(define-rgb-color bisque4 139/255 25/51 107/255)
(define-rgb-color peachpuff1 1 218/255 37/51)
(define-rgb-color peachpuff2 14/15 203/255 173/255)
(define-rgb-color peachpuff3 41/51 35/51 149/255)
(define-rgb-color peachpuff4 139/255 7/15 101/255)
(define-rgb-color navajowhite1 1 74/85 173/255)
(define-rgb-color navajowhite2 14/15 69/85 161/255)
(define-rgb-color navajowhite3 41/51 179/255 139/255)
(define-rgb-color navajowhite4 139/255 121/255 94/255)
(define-rgb-color lemonchiffon1 1 50/51 41/51)
(define-rgb-color lemonchiffon2 14/15 233/255 191/255)
(define-rgb-color lemonchiffon3 41/51 67/85 11/17)
(define-rgb-color lemonchiffon4 139/255 137/255 112/255)
(define-rgb-color cornsilk1 1 248/255 44/51)
(define-rgb-color cornsilk2 14/15 232/255 41/51)
(define-rgb-color cornsilk3 41/51 40/51 59/85)
(define-rgb-color cornsilk4 139/255 8/15 8/17)
(define-rgb-color ivory1 1 1 16/17)
(define-rgb-color ivory2 14/15 14/15 224/255)
(define-rgb-color ivory3 41/51 41/51 193/255)
(define-rgb-color ivory4 139/255 139/255 131/255)
(define-rgb-color honeydew1 16/17 1 16/17)
(define-rgb-color honeydew2 224/255 14/15 224/255)
(define-rgb-color honeydew3 193/255 41/51 193/255)
(define-rgb-color honeydew4 131/255 139/255 131/255)
(define-rgb-color lavenderblush1 1 16/17 49/51)
(define-rgb-color lavenderblush2 14/15 224/255 229/255)
(define-rgb-color lavenderblush3 41/51 193/255 197/255)
(define-rgb-color lavenderblush4 139/255 131/255 134/255)
(define-rgb-color mistyrose1 1 76/85 15/17)
(define-rgb-color mistyrose2 14/15 71/85 14/17)
(define-rgb-color mistyrose3 41/51 61/85 181/255)
(define-rgb-color mistyrose4 139/255 25/51 41/85)
(define-rgb-color azure1 16/17 1 1)
(define-rgb-color azure2 224/255 14/15 14/15)
(define-rgb-color azure3 193/255 41/51 41/51)
(define-rgb-color azure4 131/255 139/255 139/255)
(define-rgb-color slateblue1 131/255 37/85 1)
(define-rgb-color slateblue2 122/255 103/255 14/15)
(define-rgb-color slateblue3 7/17 89/255 41/51)
(define-rgb-color slateblue4 71/255 4/17 139/255)
(define-rgb-color royalblue1 24/85 118/255 1)
(define-rgb-color royalblue2 67/255 22/51 14/15)
(define-rgb-color royalblue3 58/255 19/51 41/51)
(define-rgb-color royalblue4 13/85 64/255 139/255)
(define-rgb-color blue1 0 0 1)
(define-rgb-color blue2 0 0 14/15)
(define-rgb-color blue3 0 0 41/51)
(define-rgb-color blue4 0 0 139/255)
(define-rgb-color dodgerblue1 2/17 48/85 1)
(define-rgb-color dodgerblue2 28/255 134/255 14/15)
(define-rgb-color dodgerblue3 8/85 116/255 41/51)
(define-rgb-color dodgerblue4 16/255 26/85 139/255)
(define-rgb-color steelblue1 33/85 184/255 1)
(define-rgb-color steelblue2 92/255 172/255 14/15)
(define-rgb-color steelblue3 79/255 148/255 41/51)
(define-rgb-color steelblue4 18/85 20/51 139/255)
(define-rgb-color deepskyblue1 0 191/255 1)
(define-rgb-color deepskyblue2 0 178/255 14/15)
(define-rgb-color deepskyblue3 0 154/255 41/51)
(define-rgb-color deepskyblue4 0 104/255 139/255)
(define-rgb-color skyblue1 9/17 206/255 1)
(define-rgb-color skyblue2 42/85 64/85 14/15)
(define-rgb-color skyblue3 36/85 166/255 41/51)
(define-rgb-color skyblue4 74/255 112/255 139/255)
(define-rgb-color lightskyblue1 176/255 226/255 1)
(define-rgb-color lightskyblue2 164/255 211/255 14/15)
(define-rgb-color lightskyblue3 47/85 182/255 41/51)
(define-rgb-color lightskyblue4 32/85 41/85 139/255)
(define-rgb-color slategray1 66/85 226/255 1)
(define-rgb-color slategray2 37/51 211/255 14/15)
(define-rgb-color slategray3 53/85 182/255 41/51)
(define-rgb-color slategray4 36/85 41/85 139/255)
(define-rgb-color lightsteelblue1 202/255 15/17 1)
(define-rgb-color lightsteelblue2 188/255 14/17 14/15)
(define-rgb-color lightsteelblue3 54/85 181/255 41/51)
(define-rgb-color lightsteelblue4 22/51 41/85 139/255)
(define-rgb-color lightblue1 191/255 239/255 1)
(define-rgb-color lightblue2 178/255 223/255 14/15)
(define-rgb-color lightblue3 154/255 64/85 41/51)
(define-rgb-color lightblue4 104/255 131/255 139/255)
(define-rgb-color lightcyan1 224/255 1 1)
(define-rgb-color lightcyan2 209/255 14/15 14/15)
(define-rgb-color lightcyan3 12/17 41/51 41/51)
(define-rgb-color lightcyan4 122/255 139/255 139/255)
(define-rgb-color paleturquoise1 11/15 1 1)
(define-rgb-color paleturquoise2 58/85 14/15 14/15)
(define-rgb-color paleturquoise3 10/17 41/51 41/51)
(define-rgb-color paleturquoise4 2/5 139/255 139/255)
(define-rgb-color cadetblue1 152/255 49/51 1)
(define-rgb-color cadetblue2 142/255 229/255 14/15)
(define-rgb-color cadetblue3 122/255 197/255 41/51)
(define-rgb-color cadetblue4 83/255 134/255 139/255)
(define-rgb-color turquoise1 0 49/51 1)
(define-rgb-color turquoise2 0 229/255 14/15)
(define-rgb-color turquoise3 0 197/255 41/51)
(define-rgb-color turquoise4 0 134/255 139/255)
(define-rgb-color cyan1 0 1 1)
(define-rgb-color cyan2 0 14/15 14/15)
(define-rgb-color cyan3 0 41/51 41/51)
(define-rgb-color cyan4 0 139/255 139/255)
(define-rgb-color darkslategray1 151/255 1 1)
(define-rgb-color darkslategray2 47/85 14/15 14/15)
(define-rgb-color darkslategray3 121/255 41/51 41/51)
(define-rgb-color darkslategray4 82/255 139/255 139/255)
(define-rgb-color aquamarine1 127/255 1 212/255)
(define-rgb-color aquamarine2 118/255 14/15 66/85)
(define-rgb-color aquamarine3 2/5 41/51 2/3)
(define-rgb-color aquamarine4 23/85 139/255 116/255)
(define-rgb-color darkseagreen1 193/255 1 193/255)
(define-rgb-color darkseagreen2 12/17 14/15 12/17)
(define-rgb-color darkseagreen3 31/51 41/51 31/51)
(define-rgb-color darkseagreen4 7/17 139/255 7/17)
(define-rgb-color seagreen1 28/85 1 53/85)
(define-rgb-color seagreen2 26/85 14/15 148/255)
(define-rgb-color seagreen3 67/255 41/51 128/255)
(define-rgb-color seagreen4 46/255 139/255 29/85)
(define-rgb-color palegreen1 154/255 1 154/255)
(define-rgb-color palegreen2 48/85 14/15 48/85)
(define-rgb-color palegreen3 124/255 41/51 124/255)
(define-rgb-color palegreen4 28/85 139/255 28/85)
(define-rgb-color springgreen1 0 1 127/255)
(define-rgb-color springgreen2 0 14/15 118/255)
(define-rgb-color springgreen3 0 41/51 2/5)
(define-rgb-color springgreen4 0 139/255 23/85)
(define-rgb-color green1 0 1 0)
(define-rgb-color green2 0 14/15 0)
(define-rgb-color green3 0 41/51 0)
(define-rgb-color green4 0 139/255 0)
(define-rgb-color chartreuse1 127/255 1 0)
(define-rgb-color chartreuse2 118/255 14/15 0)
(define-rgb-color chartreuse3 2/5 41/51 0)
(define-rgb-color chartreuse4 23/85 139/255 0)
(define-rgb-color olivedrab1 64/85 1 62/255)
(define-rgb-color olivedrab2 179/255 14/15 58/255)
(define-rgb-color olivedrab3 154/255 41/51 10/51)
(define-rgb-color olivedrab4 7/17 139/255 2/15)
(define-rgb-color darkolivegreen1 202/255 1 112/255)
(define-rgb-color darkolivegreen2 188/255 14/15 104/255)
(define-rgb-color darkolivegreen3 54/85 41/51 6/17)
(define-rgb-color darkolivegreen4 22/51 139/255 61/255)
(define-rgb-color khaki1 1 82/85 143/255)
(define-rgb-color khaki2 14/15 46/51 133/255)
(define-rgb-color khaki3 41/51 66/85 23/51)
(define-rgb-color khaki4 139/255 134/255 26/85)
(define-rgb-color lightgoldenrod1 1 236/255 139/255)
(define-rgb-color lightgoldenrod2 14/15 44/51 26/51)
(define-rgb-color lightgoldenrod3 41/51 38/51 112/255)
(define-rgb-color lightgoldenrod4 139/255 43/85 76/255)
(define-rgb-color lightyellow1 1 1 224/255)
(define-rgb-color lightyellow2 14/15 14/15 209/255)
(define-rgb-color lightyellow3 41/51 41/51 12/17)
(define-rgb-color lightyellow4 139/255 139/255 122/255)
(define-rgb-color yellow1 1 1 0)
(define-rgb-color yellow2 14/15 14/15 0)
(define-rgb-color yellow3 41/51 41/51 0)
(define-rgb-color yellow4 139/255 139/255 0)
(define-rgb-color gold1 1 43/51 0)
(define-rgb-color gold2 14/15 67/85 0)
(define-rgb-color gold3 41/51 173/255 0)
(define-rgb-color gold4 139/255 39/85 0)
(define-rgb-color goldenrod1 1 193/255 37/255)
(define-rgb-color goldenrod2 14/15 12/17 2/15)
(define-rgb-color goldenrod3 41/51 31/51 29/255)
(define-rgb-color goldenrod4 139/255 7/17 4/51)
(define-rgb-color darkgoldenrod1 1 37/51 1/17)
(define-rgb-color darkgoldenrod2 14/15 173/255 14/255)
(define-rgb-color darkgoldenrod3 41/51 149/255 4/85)
(define-rgb-color darkgoldenrod4 139/255 101/255 8/255)
(define-rgb-color rosybrown1 1 193/255 193/255)
(define-rgb-color rosybrown2 14/15 12/17 12/17)
(define-rgb-color rosybrown3 41/51 31/51 31/51)
(define-rgb-color rosybrown4 139/255 7/17 7/17)
(define-rgb-color indianred1 1 106/255 106/255)
(define-rgb-color indianred2 14/15 33/85 33/85)
(define-rgb-color indianred3 41/51 1/3 1/3)
(define-rgb-color indianred4 139/255 58/255 58/255)
(define-rgb-color sienna1 1 26/51 71/255)
(define-rgb-color sienna2 14/15 121/255 22/85)
(define-rgb-color sienna3 41/51 104/255 19/85)
(define-rgb-color sienna4 139/255 71/255 38/255)
(define-rgb-color burlywood1 1 211/255 31/51)
(define-rgb-color burlywood2 14/15 197/255 29/51)
(define-rgb-color burlywood3 41/51 2/3 25/51)
(define-rgb-color burlywood4 139/255 23/51 1/3)
(define-rgb-color wheat1 1 77/85 62/85)
(define-rgb-color wheat2 14/15 72/85 58/85)
(define-rgb-color wheat3 41/51 62/85 10/17)
(define-rgb-color wheat4 139/255 42/85 2/5)
(define-rgb-color tan1 1 11/17 79/255)
(define-rgb-color tan2 14/15 154/255 73/255)
(define-rgb-color tan3 41/51 133/255 21/85)
(define-rgb-color tan4 139/255 6/17 43/255)
(define-rgb-color chocolate1 1 127/255 12/85)
(define-rgb-color chocolate2 14/15 118/255 11/85)
(define-rgb-color chocolate3 41/51 2/5 29/255)
(define-rgb-color chocolate4 139/255 23/85 19/255)
(define-rgb-color firebrick1 1 16/85 16/85)
(define-rgb-color firebrick2 14/15 44/255 44/255)
(define-rgb-color firebrick3 41/51 38/255 38/255)
(define-rgb-color firebrick4 139/255 26/255 26/255)
(define-rgb-color brown1 1 64/255 64/255)
(define-rgb-color brown2 14/15 59/255 59/255)
(define-rgb-color brown3 41/51 1/5 1/5)
(define-rgb-color brown4 139/255 7/51 7/51)
(define-rgb-color salmon1 1 28/51 7/17)
(define-rgb-color salmon2 14/15 26/51 98/255)
(define-rgb-color salmon3 41/51 112/255 28/85)
(define-rgb-color salmon4 139/255 76/255 19/85)
(define-rgb-color lightsalmon1 1 32/51 122/255)
(define-rgb-color lightsalmon2 14/15 149/255 38/85)
(define-rgb-color lightsalmon3 41/51 43/85 98/255)
(define-rgb-color lightsalmon4 139/255 29/85 22/85)
(define-rgb-color orange1 1 11/17 0)
(define-rgb-color orange2 14/15 154/255 0)
(define-rgb-color orange3 41/51 133/255 0)
(define-rgb-color orange4 139/255 6/17 0)
(define-rgb-color darkorange1 1 127/255 0)
(define-rgb-color darkorange2 14/15 118/255 0)
(define-rgb-color darkorange3 41/51 2/5 0)
(define-rgb-color darkorange4 139/255 23/85 0)
(define-rgb-color coral1 1 38/85 86/255)
(define-rgb-color coral2 14/15 106/255 16/51)
(define-rgb-color coral3 41/51 91/255 23/85)
(define-rgb-color coral4 139/255 62/255 47/255)
(define-rgb-color tomato1 1 33/85 71/255)
(define-rgb-color tomato2 14/15 92/255 22/85)
(define-rgb-color tomato3 41/51 79/255 19/85)
(define-rgb-color tomato4 139/255 18/85 38/255)
(define-rgb-color orangered1 1 23/85 0)
(define-rgb-color orangered2 14/15 64/255 0)
(define-rgb-color orangered3 41/51 11/51 0)
(define-rgb-color orangered4 139/255 37/255 0)
(define-rgb-color red1 1 0 0)
(define-rgb-color red2 14/15 0 0)
(define-rgb-color red3 41/51 0 0)
(define-rgb-color red4 139/255 0 0)
(define-rgb-color debianred 43/51 7/255 27/85)
(define-rgb-color deeppink1 1 4/51 49/85)
(define-rgb-color deeppink2 14/15 6/85 137/255)
(define-rgb-color deeppink3 41/51 16/255 118/255)
(define-rgb-color deeppink4 139/255 2/51 16/51)
(define-rgb-color hotpink1 1 22/51 12/17)
(define-rgb-color hotpink2 14/15 106/255 167/255)
(define-rgb-color hotpink3 41/51 32/85 48/85)
(define-rgb-color hotpink4 139/255 58/255 98/255)
(define-rgb-color pink1 1 181/255 197/255)
(define-rgb-color pink2 14/15 169/255 184/255)
(define-rgb-color pink3 41/51 29/51 158/255)
(define-rgb-color pink4 139/255 33/85 36/85)
(define-rgb-color lightpink1 1 58/85 37/51)
(define-rgb-color lightpink2 14/15 54/85 173/255)
(define-rgb-color lightpink3 41/51 28/51 149/255)
(define-rgb-color lightpink4 139/255 19/51 101/255)
(define-rgb-color palevioletred1 1 26/51 57/85)
(define-rgb-color palevioletred2 14/15 121/255 53/85)
(define-rgb-color palevioletred3 41/51 104/255 137/255)
(define-rgb-color palevioletred4 139/255 71/255 31/85)
(define-rgb-color maroon1 1 52/255 179/255)
(define-rgb-color maroon2 14/15 16/85 167/255)
(define-rgb-color maroon3 41/51 41/255 48/85)
(define-rgb-color maroon4 139/255 28/255 98/255)
(define-rgb-color violetred1 1 62/255 10/17)
(define-rgb-color violetred2 14/15 58/255 28/51)
(define-rgb-color violetred3 41/51 10/51 8/17)
(define-rgb-color violetred4 139/255 2/15 82/255)
(define-rgb-color magenta1 1 0 1)
(define-rgb-color magenta2 14/15 0 14/15)
(define-rgb-color magenta3 41/51 0 41/51)
(define-rgb-color magenta4 139/255 0 139/255)
(define-rgb-color orchid1 1 131/255 50/51)
(define-rgb-color orchid2 14/15 122/255 233/255)
(define-rgb-color orchid3 41/51 7/17 67/85)
(define-rgb-color orchid4 139/255 71/255 137/255)
(define-rgb-color plum1 1 11/15 1)
(define-rgb-color plum2 14/15 58/85 14/15)
(define-rgb-color plum3 41/51 10/17 41/51)
(define-rgb-color plum4 139/255 2/5 139/255)
(define-rgb-color mediumorchid1 224/255 2/5 1)
(define-rgb-color mediumorchid2 209/255 19/51 14/15)
(define-rgb-color mediumorchid3 12/17 82/255 41/51)
(define-rgb-color mediumorchid4 122/255 11/51 139/255)
(define-rgb-color darkorchid1 191/255 62/255 1)
(define-rgb-color darkorchid2 178/255 58/255 14/15)
(define-rgb-color darkorchid3 154/255 10/51 41/51)
(define-rgb-color darkorchid4 104/255 2/15 139/255)
(define-rgb-color purple1 31/51 16/85 1)
(define-rgb-color purple2 29/51 44/255 14/15)
(define-rgb-color purple3 25/51 38/255 41/51)
(define-rgb-color purple4 1/3 26/255 139/255)
(define-rgb-color mediumpurple1 57/85 26/51 1)
(define-rgb-color mediumpurple2 53/85 121/255 14/15)
(define-rgb-color mediumpurple3 137/255 104/255 41/51)
(define-rgb-color mediumpurple4 31/85 71/255 139/255)
(define-rgb-color thistle1 1 15/17 1)
(define-rgb-color thistle2 14/15 14/17 14/15)
(define-rgb-color thistle3 41/51 181/255 41/51)
(define-rgb-color thistle4 139/255 41/85 139/255)
(define-rgb-color gray0 0 0 0)
(define-rgb-color grey0 0 0 0)
(define-rgb-color gray1 1/85 1/85 1/85)
(define-rgb-color grey1 1/85 1/85 1/85)
(define-rgb-color gray2 1/51 1/51 1/51)
(define-rgb-color grey2 1/51 1/51 1/51)
(define-rgb-color gray3 8/255 8/255 8/255)
(define-rgb-color grey3 8/255 8/255 8/255)
(define-rgb-color gray4 2/51 2/51 2/51)
(define-rgb-color grey4 2/51 2/51 2/51)
(define-rgb-color gray5 13/255 13/255 13/255)
(define-rgb-color grey5 13/255 13/255 13/255)
(define-rgb-color gray6 1/17 1/17 1/17)
(define-rgb-color grey6 1/17 1/17 1/17)
(define-rgb-color gray7 6/85 6/85 6/85)
(define-rgb-color grey7 6/85 6/85 6/85)
(define-rgb-color gray8 4/51 4/51 4/51)
(define-rgb-color grey8 4/51 4/51 4/51)
(define-rgb-color gray9 23/255 23/255 23/255)
(define-rgb-color grey9 23/255 23/255 23/255)
(define-rgb-color gray10 26/255 26/255 26/255)
(define-rgb-color grey10 26/255 26/255 26/255)
(define-rgb-color gray11 28/255 28/255 28/255)
(define-rgb-color grey11 28/255 28/255 28/255)
(define-rgb-color gray12 31/255 31/255 31/255)
(define-rgb-color grey12 31/255 31/255 31/255)
(define-rgb-color gray13 11/85 11/85 11/85)
(define-rgb-color grey13 11/85 11/85 11/85)
(define-rgb-color gray14 12/85 12/85 12/85)
(define-rgb-color grey14 12/85 12/85 12/85)
(define-rgb-color gray15 38/255 38/255 38/255)
(define-rgb-color grey15 38/255 38/255 38/255)
(define-rgb-color gray16 41/255 41/255 41/255)
(define-rgb-color grey16 41/255 41/255 41/255)
(define-rgb-color gray17 43/255 43/255 43/255)
(define-rgb-color grey17 43/255 43/255 43/255)
(define-rgb-color gray18 46/255 46/255 46/255)
(define-rgb-color grey18 46/255 46/255 46/255)
(define-rgb-color gray19 16/85 16/85 16/85)
(define-rgb-color grey19 16/85 16/85 16/85)
(define-rgb-color gray20 1/5 1/5 1/5)
(define-rgb-color grey20 1/5 1/5 1/5)
(define-rgb-color gray21 18/85 18/85 18/85)
(define-rgb-color grey21 18/85 18/85 18/85)
(define-rgb-color gray22 56/255 56/255 56/255)
(define-rgb-color grey22 56/255 56/255 56/255)
(define-rgb-color gray23 59/255 59/255 59/255)
(define-rgb-color grey23 59/255 59/255 59/255)
(define-rgb-color gray24 61/255 61/255 61/255)
(define-rgb-color grey24 61/255 61/255 61/255)
(define-rgb-color gray25 64/255 64/255 64/255)
(define-rgb-color grey25 64/255 64/255 64/255)
(define-rgb-color gray26 22/85 22/85 22/85)
(define-rgb-color grey26 22/85 22/85 22/85)
(define-rgb-color gray27 23/85 23/85 23/85)
(define-rgb-color grey27 23/85 23/85 23/85)
(define-rgb-color gray28 71/255 71/255 71/255)
(define-rgb-color grey28 71/255 71/255 71/255)
(define-rgb-color gray29 74/255 74/255 74/255)
(define-rgb-color grey29 74/255 74/255 74/255)
(define-rgb-color gray30 77/255 77/255 77/255)
(define-rgb-color grey30 77/255 77/255 77/255)
(define-rgb-color gray31 79/255 79/255 79/255)
(define-rgb-color grey31 79/255 79/255 79/255)
(define-rgb-color gray32 82/255 82/255 82/255)
(define-rgb-color grey32 82/255 82/255 82/255)
(define-rgb-color gray33 28/85 28/85 28/85)
(define-rgb-color grey33 28/85 28/85 28/85)
(define-rgb-color gray34 29/85 29/85 29/85)
(define-rgb-color grey34 29/85 29/85 29/85)
(define-rgb-color gray35 89/255 89/255 89/255)
(define-rgb-color grey35 89/255 89/255 89/255)
(define-rgb-color gray36 92/255 92/255 92/255)
(define-rgb-color grey36 92/255 92/255 92/255)
(define-rgb-color gray37 94/255 94/255 94/255)
(define-rgb-color grey37 94/255 94/255 94/255)
(define-rgb-color gray38 97/255 97/255 97/255)
(define-rgb-color grey38 97/255 97/255 97/255)
(define-rgb-color gray39 33/85 33/85 33/85)
(define-rgb-color grey39 33/85 33/85 33/85)
(define-rgb-color gray40 2/5 2/5 2/5)
(define-rgb-color grey40 2/5 2/5 2/5)
(define-rgb-color gray41 7/17 7/17 7/17)
(define-rgb-color grey41 7/17 7/17 7/17)
(define-rgb-color gray42 107/255 107/255 107/255)
(define-rgb-color grey42 107/255 107/255 107/255)
(define-rgb-color gray43 22/51 22/51 22/51)
(define-rgb-color grey43 22/51 22/51 22/51)
(define-rgb-color gray44 112/255 112/255 112/255)
(define-rgb-color grey44 112/255 112/255 112/255)
(define-rgb-color gray45 23/51 23/51 23/51)
(define-rgb-color grey45 23/51 23/51 23/51)
(define-rgb-color gray46 39/85 39/85 39/85)
(define-rgb-color grey46 39/85 39/85 39/85)
(define-rgb-color gray47 8/17 8/17 8/17)
(define-rgb-color grey47 8/17 8/17 8/17)
(define-rgb-color gray48 122/255 122/255 122/255)
(define-rgb-color grey48 122/255 122/255 122/255)
(define-rgb-color gray49 25/51 25/51 25/51)
(define-rgb-color grey49 25/51 25/51 25/51)
(define-rgb-color gray50 127/255 127/255 127/255)
(define-rgb-color grey50 127/255 127/255 127/255)
(define-rgb-color gray51 26/51 26/51 26/51)
(define-rgb-color grey51 26/51 26/51 26/51)
(define-rgb-color gray52 133/255 133/255 133/255)
(define-rgb-color grey52 133/255 133/255 133/255)
(define-rgb-color gray53 9/17 9/17 9/17)
(define-rgb-color grey53 9/17 9/17 9/17)
(define-rgb-color gray54 46/85 46/85 46/85)
(define-rgb-color grey54 46/85 46/85 46/85)
(define-rgb-color gray55 28/51 28/51 28/51)
(define-rgb-color grey55 28/51 28/51 28/51)
(define-rgb-color gray56 143/255 143/255 143/255)
(define-rgb-color grey56 143/255 143/255 143/255)
(define-rgb-color gray57 29/51 29/51 29/51)
(define-rgb-color grey57 29/51 29/51 29/51)
(define-rgb-color gray58 148/255 148/255 148/255)
(define-rgb-color grey58 148/255 148/255 148/255)
(define-rgb-color gray59 10/17 10/17 10/17)
(define-rgb-color grey59 10/17 10/17 10/17)
(define-rgb-color gray60 3/5 3/5 3/5)
(define-rgb-color grey60 3/5 3/5 3/5)
(define-rgb-color gray61 52/85 52/85 52/85)
(define-rgb-color grey61 52/85 52/85 52/85)
(define-rgb-color gray62 158/255 158/255 158/255)
(define-rgb-color grey62 158/255 158/255 158/255)
(define-rgb-color gray63 161/255 161/255 161/255)
(define-rgb-color grey63 161/255 161/255 161/255)
(define-rgb-color gray64 163/255 163/255 163/255)
(define-rgb-color grey64 163/255 163/255 163/255)
(define-rgb-color gray65 166/255 166/255 166/255)
(define-rgb-color grey65 166/255 166/255 166/255)
(define-rgb-color gray66 56/85 56/85 56/85)
(define-rgb-color grey66 56/85 56/85 56/85)
(define-rgb-color gray67 57/85 57/85 57/85)
(define-rgb-color grey67 57/85 57/85 57/85)
(define-rgb-color gray68 173/255 173/255 173/255)
(define-rgb-color grey68 173/255 173/255 173/255)
(define-rgb-color gray69 176/255 176/255 176/255)
(define-rgb-color grey69 176/255 176/255 176/255)
(define-rgb-color gray70 179/255 179/255 179/255)
(define-rgb-color grey70 179/255 179/255 179/255)
(define-rgb-color gray71 181/255 181/255 181/255)
(define-rgb-color grey71 181/255 181/255 181/255)
(define-rgb-color gray72 184/255 184/255 184/255)
(define-rgb-color grey72 184/255 184/255 184/255)
(define-rgb-color gray73 62/85 62/85 62/85)
(define-rgb-color grey73 62/85 62/85 62/85)
(define-rgb-color gray74 63/85 63/85 63/85)
(define-rgb-color grey74 63/85 63/85 63/85)
(define-rgb-color gray75 191/255 191/255 191/255)
(define-rgb-color grey75 191/255 191/255 191/255)
(define-rgb-color gray76 194/255 194/255 194/255)
(define-rgb-color grey76 194/255 194/255 194/255)
(define-rgb-color gray77 196/255 196/255 196/255)
(define-rgb-color grey77 196/255 196/255 196/255)
(define-rgb-color gray78 199/255 199/255 199/255)
(define-rgb-color grey78 199/255 199/255 199/255)
(define-rgb-color gray79 67/85 67/85 67/85)
(define-rgb-color grey79 67/85 67/85 67/85)
(define-rgb-color gray80 4/5 4/5 4/5)
(define-rgb-color grey80 4/5 4/5 4/5)
(define-rgb-color gray81 69/85 69/85 69/85)
(define-rgb-color grey81 69/85 69/85 69/85)
(define-rgb-color gray82 209/255 209/255 209/255)
(define-rgb-color grey82 209/255 209/255 209/255)
(define-rgb-color gray83 212/255 212/255 212/255)
(define-rgb-color grey83 212/255 212/255 212/255)
(define-rgb-color gray84 214/255 214/255 214/255)
(define-rgb-color grey84 214/255 214/255 214/255)
(define-rgb-color gray85 217/255 217/255 217/255)
(define-rgb-color grey85 217/255 217/255 217/255)
(define-rgb-color gray86 73/85 73/85 73/85)
(define-rgb-color grey86 73/85 73/85 73/85)
(define-rgb-color gray87 74/85 74/85 74/85)
(define-rgb-color grey87 74/85 74/85 74/85)
(define-rgb-color gray88 224/255 224/255 224/255)
(define-rgb-color grey88 224/255 224/255 224/255)
(define-rgb-color gray89 227/255 227/255 227/255)
(define-rgb-color grey89 227/255 227/255 227/255)
(define-rgb-color gray90 229/255 229/255 229/255)
(define-rgb-color grey90 229/255 229/255 229/255)
(define-rgb-color gray91 232/255 232/255 232/255)
(define-rgb-color grey91 232/255 232/255 232/255)
(define-rgb-color gray92 47/51 47/51 47/51)
(define-rgb-color grey92 47/51 47/51 47/51)
(define-rgb-color gray93 79/85 79/85 79/85)
(define-rgb-color grey93 79/85 79/85 79/85)
(define-rgb-color gray94 16/17 16/17 16/17)
(define-rgb-color grey94 16/17 16/17 16/17)
(define-rgb-color gray95 242/255 242/255 242/255)
(define-rgb-color grey95 242/255 242/255 242/255)
(define-rgb-color gray96 49/51 49/51 49/51)
(define-rgb-color grey96 49/51 49/51 49/51)
(define-rgb-color gray97 247/255 247/255 247/255)
(define-rgb-color grey97 247/255 247/255 247/255)
(define-rgb-color gray98 50/51 50/51 50/51)
(define-rgb-color grey98 50/51 50/51 50/51)
(define-rgb-color gray99 84/85 84/85 84/85)
(define-rgb-color grey99 84/85 84/85 84/85)
(define-rgb-color gray100 1 1 1)
(define-rgb-color grey100 1 1 1)
(define-rgb-color darkgrey 169/255 169/255 169/255)
(define-rgb-color darkgray 169/255 169/255 169/255)
(define-rgb-color darkblue 0 0 139/255)
(define-rgb-color darkcyan 0 139/255 139/255)
(define-rgb-color darkmagenta 139/255 0 139/255)
(define-rgb-color darkred 139/255 0 0)
(define-rgb-color lightgreen 48/85 14/15 48/85)

View file

@ -0,0 +1,167 @@
(in-package :cl-colors)
;;; color representations
(deftype unit-real ()
"Real number in [0,1]."
'(real 0 1))
(defstruct (rgb (:constructor rgb (red green blue)))
"RGB color."
(red nil :type unit-real :read-only t)
(green nil :type unit-real :read-only t)
(blue nil :type unit-real :read-only t))
(defmethod make-load-form ((p rgb) &optional env)
(declare (ignore env))
(make-load-form-saving-slots p))
(defun gray (value)
"Create an RGB representation of a gray color (value in [0,1)."
(rgb value value value))
(define-structure-let+ (rgb) red green blue)
(defstruct (hsv (:constructor hsv (hue saturation value)))
"HSV color."
(hue nil :type (real 0 360) :read-only t)
(saturation nil :type unit-real :read-only t)
(value nil :type unit-real :read-only t))
(defmethod make-load-form ((p hsv) &optional env)
(declare (ignore env))
(make-load-form-saving-slots p))
(define-structure-let+ (hsv) hue saturation value)
(defun normalize-hue (hue)
"Normalize hue to the interval [0,360)."
(mod hue 360))
;;; conversions
(defun rgb-to-hsv (rgb &optional (undefined-hue 0))
"Convert RGB to HSV representation. When hue is undefined (saturation is
zero), UNDEFINED-HUE will be assigned."
(let+ (((&rgb red green blue) rgb)
(value (max red green blue))
(delta (- value (min red green blue)))
(saturation (if (plusp value)
(/ delta value)
0))
((&flet normalize (constant right left)
(let ((hue (+ constant (/ (* 60 (- right left)) delta))))
(if (minusp hue)
(+ hue 360)
hue)))))
(hsv (cond
((zerop saturation) undefined-hue) ; undefined
((= red value) (normalize 0 green blue)) ; dominant red
((= green value) (normalize 120 blue red)) ; dominant green
(t (normalize 240 red green)))
saturation
value)))
(defun hsv-to-rgb (hsv)
"Convert HSV to RGB representation. When SATURATION is zero, HUE is
ignored."
(let+ (((&hsv hue saturation value) hsv))
;; if saturation=0, color is on the gray line
(when (zerop saturation)
(return-from hsv-to-rgb (gray value)))
;; nonzero saturation: normalize hue to [0,6)
(let+ ((h (/ (normalize-hue hue) 60))
((&values quotient remainder) (floor h))
(p (* value (- 1 saturation)))
(q (* value (- 1 (* saturation remainder))))
(r (* value (- 1 (* saturation (- 1 remainder)))))
((&values red green blue) (case quotient
(0 (values value r p))
(1 (values q value p))
(2 (values p value r))
(3 (values p q value))
(4 (values r p value))
(t (values value p q)))))
(rgb red green blue))))
(defun hex-to-rgb (string)
"Parse hexadecimal notation (eg ff0000 or f00 for red) into an RGB color."
(let+ (((&values width max)
(case (length string)
(3 (values 1 15))
(6 (values 2 255))
(t (error "string ~A doesn't have length 3 or 6, can't parse as ~
RGB specification" string))))
((&flet parse (index)
(/ (parse-integer string :start (* index width)
:end (* (1+ index) width)
:radix 16)
max))))
(rgb (parse 0) (parse 1) (parse 2))))
;;; conversion with generic functions
(defgeneric as-hsv (color &optional undefined-hue)
(:method ((color rgb) &optional (undefined-hue 0))
(rgb-to-hsv color undefined-hue))
(:method ((color hsv) &optional undefined-hue)
(declare (ignore undefined-hue))
color))
(defgeneric as-rgb (color)
(:method ((rgb rgb))
rgb)
(:method ((hsv hsv))
(hsv-to-rgb hsv))
(:method ((string string))
;; TODO in the long run this should recognize color names too
(hex-to-rgb string)))
;;; combinations
;;; internal functions
(declaim (inline cc))
(defun cc (a b alpha)
"Convex combination (1-ALPHA)*A+ALPHA*B, ie ALPHA is the weight of A."
(declare (type (real 0 1) alpha))
(+ (* (- 1 alpha) a) (* alpha b)))
(defun rgb-combination (color1 color2 alpha)
"Color combination in RGB space."
(let+ (((&rgb red1 green1 blue1) (as-rgb color1))
((&rgb red2 green2 blue2) (as-rgb color2))
((&flet c (c1 c2) (cc c1 c2 alpha))))
(rgb (c red1 red2)
(c green1 green2)
(c blue1 blue2))))
(defun hsv-combination (hsv1 hsv2 alpha &optional (positive? t))
"Color combination in HSV space. POSITIVE? determines whether the hue
combination is in the positive or negative direction on the color wheel."
(let+ (((&hsv hue1 saturation1 value1) (as-hsv hsv1))
((&hsv hue2 saturation2 value2) (as-hsv hsv2))
((&flet c (c1 c2) (cc c1 c2 alpha))))
(hsv (cond
((and positive? (> hue1 hue2))
(normalize-hue (c hue1 (+ hue2 360))))
((and (not positive?) (< hue1 hue2))
(normalize-hue (c (+ hue1 360) hue2)))
(t (c hue1 hue2)))
(c saturation1 saturation2)
(c value1 value2))))
;;; macros used by the autogenerated files
(defmacro define-rgb-color (name red green blue)
"Macro for defining color constants. Used by the automatically generated color file."
(let ((constant-name (symbolicate #\+ name #\+)))
`(progn
(define-constant ,constant-name (rgb ,red ,green ,blue)
:test #'equalp :documentation ,(format nil "X11 color ~A." name)))))

View file

@ -0,0 +1,69 @@
(in-package #:cl-colors)
;;; parsing and printing of CSS-like colors
(defun print-hex-rgb (color &key short (hash T) alpha destination)
"Converts a COLOR to its hexadecimal RGB string representation. If
SHORT is specified each component gets just one character.
A hash character (#) is prepended if HASH is true (default).
If ALPHA is set it is included as an ALPHA component.
DESTINATION is the first argument to FORMAT, by default NIL."
(let+ (((&rgb red green blue) (as-rgb color))
(factor (if short 15 255))
((&flet c (x) (round (* x factor)))))
(format destination (if short
"~@[~C~]~X~X~X~@[~X~]"
"~@[~C~]~2,'0X~2,'0X~2,'0X~@[~X~]")
(and hash #\#)
(c red) (c green) (c blue)
(and alpha (c alpha)))))
;; TODO: a JUNK-ALLOWED parameter, like for PARSE-INTEGER, would be nice
(defun parse-hex-rgb (string &key (start 0) end)
"Parses a hexadecimal RGB(A) color string. Returns a new RGB color value
and an alpha component if present."
(let* ((length (length string))
(end (or end length))
(sub-length (- end start)))
(cond
;; check for valid range, we need at least three and accept at most
;; nine characters
((and (<= #.(length "fff") sub-length)
(<= sub-length #.(length "#ffffff00")))
(when (char= (char string start) #\#)
(incf start)
(decf sub-length))
(labels ((parse (string index offset)
(parse-integer string :start index :end (+ offset index)
:radix 16))
(short (string index)
(/ (parse string index 1) 15))
(long (string index)
(/ (parse string index 2) 255)))
;; recognize possible combinations of alpha component and length
;; of the rest of the encoded color
(multiple-value-bind (shortp alphap)
(case sub-length
(#.(length "fff") (values T NIL))
(#.(length "fff0") (values T T))
(#.(length "ffffff") (values NIL NIL))
(#.(length "ffffff00") (values NIL T)))
(if shortp
(values
(rgb
(short string start)
(short string (+ 1 start))
(short string (+ 2 start)))
(and alphap (short string (+ 3 start))))
(values
(rgb
(long string start)
(long string (+ 2 start))
(long string (+ 4 start)))
(and alphap (long string (+ 6 start))))))))
(T
(error "not enough or too many characters in indicated sequence: ~A"
(subseq string start end))))))

View file

@ -0,0 +1,52 @@
Color classes
-------------
The two main color classes are rgb and hsv, which have slots red,
green, blue and hue, saturation, value respectively. There is also an
rgb class with an alpha channel (slot alpha) called rgba. In the rgb
class, valid slot values are from 0 to 1, while in the hsv class,
saturation and value are in the interval [0,1], but hue is in [0,360).
You can convert between rgb and hsv using rgb->hsv and hsv->rgb. Note
that for the former, you need to specify what happens when the hue is
undefined (ie the color is gray). By default, the hue of red (0) is
assigned.
Generic functions which find the appropriate conversion method are
available with names ->rgb and ->hsv. Use these if you want your
functions to handle various different color representations but
eventually you need to work with a single one.
Named colors
------------
Named colors, parsed from the X11 colors file, are loaded from
colornames.lisp. As they are constants, names are between +'s. All
named colors are rgb.
Convex combinations
-------------------
Use hsv-combination or rgb-combination for taking convex combinations
in the respective color space. Note that in the HSV space, you need
to specify the direction on the color wheel, the default is positive.
Example session
---------------
CL-COLORS> +blue+
#<RGB red: 0.0d0 green: 0.0d0 blue: 1.0d0>
CL-COLORS> (->hsv +blue+)
#<HSV hue: 240.0d0 saturation: 1.0d0 value: 1.0d0>
CL-COLORS> (rgb-combination +blue+ +green+ 0.5)
#<RGB red: 0.0d0 green: 0.5d0 blue: 0.5d0>
CL-COLORS> (->rgb (hsv-combination (->hsv +blue+) (->hsv +green+) 0.5))
#<RGB red: 1.0d0 green: 0.0d0 blue: 0.0d0>
CL-COLORS> (->rgb (hsv-combination (->hsv +blue+) (->hsv +green+) 0.5 nil))
#<RGB red: 0.0d0 green: 1.0d0 blue: 1.0d0>

View file

@ -0,0 +1,17 @@
;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; -*-
(in-package #:common-lisp-user)
(defpackage #:cl-colors
(:use #:alexandria
#:common-lisp
#:let-plus)
(:export
#:rgb #:rgb-red #:rgb-green #:rgb-blue #:gray #:&rgb
#:hsv #:hsv-hue #:hsv-saturation #:hsv-value #:&hsv
#:rgb-to-hsv #:hsv-to-rgb #:hex-to-rgb #:as-hsv #:as-rgb
#:rgb-combination #:hsv-combination
#:parse-hex-rgb #:print-hex-rgb
;; predefined color names
~A))

View file

@ -0,0 +1,674 @@
;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; -*-
(in-package #:common-lisp-user)
(defpackage #:cl-colors
(:use #:alexandria
#:common-lisp
#:let-plus)
(:export
#:rgb #:rgb-red #:rgb-green #:rgb-blue #:gray #:&rgb
#:hsv #:hsv-hue #:hsv-saturation #:hsv-value #:&hsv
#:rgb-to-hsv #:hsv-to-rgb #:hex-to-rgb #:as-hsv #:as-rgb
#:rgb-combination #:hsv-combination
#:parse-hex-rgb #:print-hex-rgb
;; predefined color names
#:+snow+
#:+ghostwhite+
#:+whitesmoke+
#:+gainsboro+
#:+floralwhite+
#:+oldlace+
#:+linen+
#:+antiquewhite+
#:+papayawhip+
#:+blanchedalmond+
#:+bisque+
#:+peachpuff+
#:+navajowhite+
#:+moccasin+
#:+cornsilk+
#:+ivory+
#:+lemonchiffon+
#:+seashell+
#:+honeydew+
#:+mintcream+
#:+azure+
#:+aliceblue+
#:+lavender+
#:+lavenderblush+
#:+mistyrose+
#:+white+
#:+black+
#:+darkslategray+
#:+darkslategrey+
#:+dimgray+
#:+dimgrey+
#:+slategray+
#:+slategrey+
#:+lightslategray+
#:+lightslategrey+
#:+gray+
#:+grey+
#:+lightgrey+
#:+lightgray+
#:+midnightblue+
#:+navy+
#:+navyblue+
#:+cornflowerblue+
#:+darkslateblue+
#:+slateblue+
#:+mediumslateblue+
#:+lightslateblue+
#:+mediumblue+
#:+royalblue+
#:+blue+
#:+dodgerblue+
#:+deepskyblue+
#:+skyblue+
#:+lightskyblue+
#:+steelblue+
#:+lightsteelblue+
#:+lightblue+
#:+powderblue+
#:+paleturquoise+
#:+darkturquoise+
#:+mediumturquoise+
#:+turquoise+
#:+cyan+
#:+lightcyan+
#:+cadetblue+
#:+mediumaquamarine+
#:+aquamarine+
#:+darkgreen+
#:+darkolivegreen+
#:+darkseagreen+
#:+seagreen+
#:+mediumseagreen+
#:+lightseagreen+
#:+palegreen+
#:+springgreen+
#:+lawngreen+
#:+green+
#:+chartreuse+
#:+mediumspringgreen+
#:+greenyellow+
#:+limegreen+
#:+yellowgreen+
#:+forestgreen+
#:+olivedrab+
#:+darkkhaki+
#:+khaki+
#:+palegoldenrod+
#:+lightgoldenrodyellow+
#:+lightyellow+
#:+yellow+
#:+gold+
#:+lightgoldenrod+
#:+goldenrod+
#:+darkgoldenrod+
#:+rosybrown+
#:+indianred+
#:+saddlebrown+
#:+sienna+
#:+peru+
#:+burlywood+
#:+beige+
#:+wheat+
#:+sandybrown+
#:+tan+
#:+chocolate+
#:+firebrick+
#:+brown+
#:+darksalmon+
#:+salmon+
#:+lightsalmon+
#:+orange+
#:+darkorange+
#:+coral+
#:+lightcoral+
#:+tomato+
#:+orangered+
#:+red+
#:+hotpink+
#:+deeppink+
#:+pink+
#:+lightpink+
#:+palevioletred+
#:+maroon+
#:+mediumvioletred+
#:+violetred+
#:+magenta+
#:+violet+
#:+plum+
#:+orchid+
#:+mediumorchid+
#:+darkorchid+
#:+darkviolet+
#:+blueviolet+
#:+purple+
#:+mediumpurple+
#:+thistle+
#:+snow1+
#:+snow2+
#:+snow3+
#:+snow4+
#:+seashell1+
#:+seashell2+
#:+seashell3+
#:+seashell4+
#:+antiquewhite1+
#:+antiquewhite2+
#:+antiquewhite3+
#:+antiquewhite4+
#:+bisque1+
#:+bisque2+
#:+bisque3+
#:+bisque4+
#:+peachpuff1+
#:+peachpuff2+
#:+peachpuff3+
#:+peachpuff4+
#:+navajowhite1+
#:+navajowhite2+
#:+navajowhite3+
#:+navajowhite4+
#:+lemonchiffon1+
#:+lemonchiffon2+
#:+lemonchiffon3+
#:+lemonchiffon4+
#:+cornsilk1+
#:+cornsilk2+
#:+cornsilk3+
#:+cornsilk4+
#:+ivory1+
#:+ivory2+
#:+ivory3+
#:+ivory4+
#:+honeydew1+
#:+honeydew2+
#:+honeydew3+
#:+honeydew4+
#:+lavenderblush1+
#:+lavenderblush2+
#:+lavenderblush3+
#:+lavenderblush4+
#:+mistyrose1+
#:+mistyrose2+
#:+mistyrose3+
#:+mistyrose4+
#:+azure1+
#:+azure2+
#:+azure3+
#:+azure4+
#:+slateblue1+
#:+slateblue2+
#:+slateblue3+
#:+slateblue4+
#:+royalblue1+
#:+royalblue2+
#:+royalblue3+
#:+royalblue4+
#:+blue1+
#:+blue2+
#:+blue3+
#:+blue4+
#:+dodgerblue1+
#:+dodgerblue2+
#:+dodgerblue3+
#:+dodgerblue4+
#:+steelblue1+
#:+steelblue2+
#:+steelblue3+
#:+steelblue4+
#:+deepskyblue1+
#:+deepskyblue2+
#:+deepskyblue3+
#:+deepskyblue4+
#:+skyblue1+
#:+skyblue2+
#:+skyblue3+
#:+skyblue4+
#:+lightskyblue1+
#:+lightskyblue2+
#:+lightskyblue3+
#:+lightskyblue4+
#:+slategray1+
#:+slategray2+
#:+slategray3+
#:+slategray4+
#:+lightsteelblue1+
#:+lightsteelblue2+
#:+lightsteelblue3+
#:+lightsteelblue4+
#:+lightblue1+
#:+lightblue2+
#:+lightblue3+
#:+lightblue4+
#:+lightcyan1+
#:+lightcyan2+
#:+lightcyan3+
#:+lightcyan4+
#:+paleturquoise1+
#:+paleturquoise2+
#:+paleturquoise3+
#:+paleturquoise4+
#:+cadetblue1+
#:+cadetblue2+
#:+cadetblue3+
#:+cadetblue4+
#:+turquoise1+
#:+turquoise2+
#:+turquoise3+
#:+turquoise4+
#:+cyan1+
#:+cyan2+
#:+cyan3+
#:+cyan4+
#:+darkslategray1+
#:+darkslategray2+
#:+darkslategray3+
#:+darkslategray4+
#:+aquamarine1+
#:+aquamarine2+
#:+aquamarine3+
#:+aquamarine4+
#:+darkseagreen1+
#:+darkseagreen2+
#:+darkseagreen3+
#:+darkseagreen4+
#:+seagreen1+
#:+seagreen2+
#:+seagreen3+
#:+seagreen4+
#:+palegreen1+
#:+palegreen2+
#:+palegreen3+
#:+palegreen4+
#:+springgreen1+
#:+springgreen2+
#:+springgreen3+
#:+springgreen4+
#:+green1+
#:+green2+
#:+green3+
#:+green4+
#:+chartreuse1+
#:+chartreuse2+
#:+chartreuse3+
#:+chartreuse4+
#:+olivedrab1+
#:+olivedrab2+
#:+olivedrab3+
#:+olivedrab4+
#:+darkolivegreen1+
#:+darkolivegreen2+
#:+darkolivegreen3+
#:+darkolivegreen4+
#:+khaki1+
#:+khaki2+
#:+khaki3+
#:+khaki4+
#:+lightgoldenrod1+
#:+lightgoldenrod2+
#:+lightgoldenrod3+
#:+lightgoldenrod4+
#:+lightyellow1+
#:+lightyellow2+
#:+lightyellow3+
#:+lightyellow4+
#:+yellow1+
#:+yellow2+
#:+yellow3+
#:+yellow4+
#:+gold1+
#:+gold2+
#:+gold3+
#:+gold4+
#:+goldenrod1+
#:+goldenrod2+
#:+goldenrod3+
#:+goldenrod4+
#:+darkgoldenrod1+
#:+darkgoldenrod2+
#:+darkgoldenrod3+
#:+darkgoldenrod4+
#:+rosybrown1+
#:+rosybrown2+
#:+rosybrown3+
#:+rosybrown4+
#:+indianred1+
#:+indianred2+
#:+indianred3+
#:+indianred4+
#:+sienna1+
#:+sienna2+
#:+sienna3+
#:+sienna4+
#:+burlywood1+
#:+burlywood2+
#:+burlywood3+
#:+burlywood4+
#:+wheat1+
#:+wheat2+
#:+wheat3+
#:+wheat4+
#:+tan1+
#:+tan2+
#:+tan3+
#:+tan4+
#:+chocolate1+
#:+chocolate2+
#:+chocolate3+
#:+chocolate4+
#:+firebrick1+
#:+firebrick2+
#:+firebrick3+
#:+firebrick4+
#:+brown1+
#:+brown2+
#:+brown3+
#:+brown4+
#:+salmon1+
#:+salmon2+
#:+salmon3+
#:+salmon4+
#:+lightsalmon1+
#:+lightsalmon2+
#:+lightsalmon3+
#:+lightsalmon4+
#:+orange1+
#:+orange2+
#:+orange3+
#:+orange4+
#:+darkorange1+
#:+darkorange2+
#:+darkorange3+
#:+darkorange4+
#:+coral1+
#:+coral2+
#:+coral3+
#:+coral4+
#:+tomato1+
#:+tomato2+
#:+tomato3+
#:+tomato4+
#:+orangered1+
#:+orangered2+
#:+orangered3+
#:+orangered4+
#:+red1+
#:+red2+
#:+red3+
#:+red4+
#:+debianred+
#:+deeppink1+
#:+deeppink2+
#:+deeppink3+
#:+deeppink4+
#:+hotpink1+
#:+hotpink2+
#:+hotpink3+
#:+hotpink4+
#:+pink1+
#:+pink2+
#:+pink3+
#:+pink4+
#:+lightpink1+
#:+lightpink2+
#:+lightpink3+
#:+lightpink4+
#:+palevioletred1+
#:+palevioletred2+
#:+palevioletred3+
#:+palevioletred4+
#:+maroon1+
#:+maroon2+
#:+maroon3+
#:+maroon4+
#:+violetred1+
#:+violetred2+
#:+violetred3+
#:+violetred4+
#:+magenta1+
#:+magenta2+
#:+magenta3+
#:+magenta4+
#:+orchid1+
#:+orchid2+
#:+orchid3+
#:+orchid4+
#:+plum1+
#:+plum2+
#:+plum3+
#:+plum4+
#:+mediumorchid1+
#:+mediumorchid2+
#:+mediumorchid3+
#:+mediumorchid4+
#:+darkorchid1+
#:+darkorchid2+
#:+darkorchid3+
#:+darkorchid4+
#:+purple1+
#:+purple2+
#:+purple3+
#:+purple4+
#:+mediumpurple1+
#:+mediumpurple2+
#:+mediumpurple3+
#:+mediumpurple4+
#:+thistle1+
#:+thistle2+
#:+thistle3+
#:+thistle4+
#:+gray0+
#:+grey0+
#:+gray1+
#:+grey1+
#:+gray2+
#:+grey2+
#:+gray3+
#:+grey3+
#:+gray4+
#:+grey4+
#:+gray5+
#:+grey5+
#:+gray6+
#:+grey6+
#:+gray7+
#:+grey7+
#:+gray8+
#:+grey8+
#:+gray9+
#:+grey9+
#:+gray10+
#:+grey10+
#:+gray11+
#:+grey11+
#:+gray12+
#:+grey12+
#:+gray13+
#:+grey13+
#:+gray14+
#:+grey14+
#:+gray15+
#:+grey15+
#:+gray16+
#:+grey16+
#:+gray17+
#:+grey17+
#:+gray18+
#:+grey18+
#:+gray19+
#:+grey19+
#:+gray20+
#:+grey20+
#:+gray21+
#:+grey21+
#:+gray22+
#:+grey22+
#:+gray23+
#:+grey23+
#:+gray24+
#:+grey24+
#:+gray25+
#:+grey25+
#:+gray26+
#:+grey26+
#:+gray27+
#:+grey27+
#:+gray28+
#:+grey28+
#:+gray29+
#:+grey29+
#:+gray30+
#:+grey30+
#:+gray31+
#:+grey31+
#:+gray32+
#:+grey32+
#:+gray33+
#:+grey33+
#:+gray34+
#:+grey34+
#:+gray35+
#:+grey35+
#:+gray36+
#:+grey36+
#:+gray37+
#:+grey37+
#:+gray38+
#:+grey38+
#:+gray39+
#:+grey39+
#:+gray40+
#:+grey40+
#:+gray41+
#:+grey41+
#:+gray42+
#:+grey42+
#:+gray43+
#:+grey43+
#:+gray44+
#:+grey44+
#:+gray45+
#:+grey45+
#:+gray46+
#:+grey46+
#:+gray47+
#:+grey47+
#:+gray48+
#:+grey48+
#:+gray49+
#:+grey49+
#:+gray50+
#:+grey50+
#:+gray51+
#:+grey51+
#:+gray52+
#:+grey52+
#:+gray53+
#:+grey53+
#:+gray54+
#:+grey54+
#:+gray55+
#:+grey55+
#:+gray56+
#:+grey56+
#:+gray57+
#:+grey57+
#:+gray58+
#:+grey58+
#:+gray59+
#:+grey59+
#:+gray60+
#:+grey60+
#:+gray61+
#:+grey61+
#:+gray62+
#:+grey62+
#:+gray63+
#:+grey63+
#:+gray64+
#:+grey64+
#:+gray65+
#:+grey65+
#:+gray66+
#:+grey66+
#:+gray67+
#:+grey67+
#:+gray68+
#:+grey68+
#:+gray69+
#:+grey69+
#:+gray70+
#:+grey70+
#:+gray71+
#:+grey71+
#:+gray72+
#:+grey72+
#:+gray73+
#:+grey73+
#:+gray74+
#:+grey74+
#:+gray75+
#:+grey75+
#:+gray76+
#:+grey76+
#:+gray77+
#:+grey77+
#:+gray78+
#:+grey78+
#:+gray79+
#:+grey79+
#:+gray80+
#:+grey80+
#:+gray81+
#:+grey81+
#:+gray82+
#:+grey82+
#:+gray83+
#:+grey83+
#:+gray84+
#:+grey84+
#:+gray85+
#:+grey85+
#:+gray86+
#:+grey86+
#:+gray87+
#:+grey87+
#:+gray88+
#:+grey88+
#:+gray89+
#:+grey89+
#:+gray90+
#:+grey90+
#:+gray91+
#:+grey91+
#:+gray92+
#:+grey92+
#:+gray93+
#:+grey93+
#:+gray94+
#:+grey94+
#:+gray95+
#:+grey95+
#:+gray96+
#:+grey96+
#:+gray97+
#:+grey97+
#:+gray98+
#:+grey98+
#:+gray99+
#:+grey99+
#:+gray100+
#:+grey100+
#:+darkgrey+
#:+darkgray+
#:+darkblue+
#:+darkcyan+
#:+darkmagenta+
#:+darkred+
#:+lightgreen+))

View file

@ -0,0 +1,68 @@
;;; parse X11's rgb.txt
;;;
;;; no packages defined as this should just be run as a script.
(require :cl-ppcre)
(require :alexandria)
(defun write-package-file (colornames
&key (package-template-path "package-template.lisp")
(package-file-path "package.lisp"))
"Write a package definition file, exporting COLORNAMES, using the given template."
(let* ((package-template (alexandria:read-file-into-string package-template-path))
(colornames-export
(reduce (lambda (a b) (format nil "~A~%~A" a b))
colornames
:key (lambda (colorname)
(format nil " #:+~A+" colorname)))))
(with-open-file (package-file package-file-path
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(format package-file package-template colornames-export))
(values)))
(defun parse-and-write-color-definitions (&key
(source-path "/usr/share/X11/rgb.txt")
(destination-path "colornames.lisp"))
"Parse color definitions and write them into a file. Return the list of colors (for exporting)."
(let ((color-scanner ; will only take names w/o spaces
(cl-ppcre:create-scanner
"^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+([\\s\\w]+\?)\\s*$"
:extended-mode t))
(comment-scanner (cl-ppcre:create-scanner "^\\s*!"))
colornames)
(with-open-file (source source-path
:direction :input
:if-does-not-exist :error)
(with-open-file (colordefs destination-path
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(format colordefs ";;;; This file was generated automatically ~
by parse-x11.lisp~%~
;;;; Please do not edit directly, just run make if necessary (but should not be).~2%~
(in-package #:cl-colors)~2%")
(labels ((parse-channel (string)
(let ((i (read-from-string string)))
(assert (and (typep i 'integer) (<= i 255)))
(/ i 255))))
(do ((line (read-line source nil nil) (read-line source nil nil)))
((not line))
(unless (cl-ppcre:scan-to-strings comment-scanner line)
(multiple-value-bind (match registers)
(cl-ppcre:scan-to-strings color-scanner line)
(if (and match (not (find #\space (aref registers 3))))
(let ((colorname (string-downcase (aref registers 3))))
(format colordefs
"(define-rgb-color ~A ~A ~A ~A)~%"
colorname
(parse-channel (aref registers 0))
(parse-channel (aref registers 1))
(parse-channel (aref registers 2)))
(push colorname colornames))
(format t "ignoring line ~A~%" line)))))))
(nreverse colornames))))
(let ((colornames (parse-and-write-color-definitions)))
(write-package-file colornames))

View file

@ -0,0 +1,82 @@
(in-package #:cl-user)
(defpackage #:cl-colors-tests
(:use #:alexandria #:common-lisp #:cl-colors #:let-plus #:lift)
(:export #:run))
(in-package #:cl-colors-tests)
(deftestsuite cl-colors-tests () ())
(defun run ()
"Run all the tests for CL-COLORS-TESTS."
(run-tests :suite 'cl-colors-tests))
(defun eps= (a b &optional (epsilon 1e-10))
(<= (abs (- a b)) epsilon))
(defun rgb= (rgb1 rgb2 &optional (epsilon 1e-10))
"Compare RGB colors for (numerical) equality."
(let+ (((&rgb red1 green1 blue1) rgb1)
((&rgb red2 green2 blue2) rgb2))
(and (eps= red1 red2 epsilon)
(eps= green1 green2 epsilon)
(eps= blue1 blue2 epsilon))))
(defun random-rgb ()
(rgb (random 1d0) (random 1d0) (random 1d0)))
(addtest (cl-colors-tests)
rgb<->hsv
(loop repeat 100 do
(let ((rgb (random-rgb)))
(ensure-same rgb (as-rgb (as-hsv rgb)) :test #'rgb=))))
;; (defun test-hue-combination (from to positivep)
;; (dotimes (i 21)
;; (format t "~a " (hue-combination from to (/ i 20) positivep))))
(addtest (cl-colors-tests)
print-hex-rgb
(let ((rgb (rgb 0.070 0.203 0.337)))
(ensure-same "#123456" (print-hex-rgb rgb))
(ensure-same "123456" (print-hex-rgb rgb :hash nil))
(ensure-same "#135" (print-hex-rgb rgb :short t))
(ensure-same "135" (print-hex-rgb rgb :hash nil :short t))
(ensure-same "#12345678" (print-hex-rgb rgb :alpha 0.47))
(ensure-same "12345678" (print-hex-rgb rgb :alpha 0.47 :hash nil))
(ensure-same "#1357" (print-hex-rgb rgb :alpha 0.47 :short t))
(ensure-same "1357" (print-hex-rgb rgb :alpha 0.47 :hash nil :short t))))
(addtest (cl-colors-tests)
parse-hex-rgb
(let ((rgb (rgb 0.070 0.203 0.337)))
(ensure-same rgb (parse-hex-rgb "#123456") :test (rcurry #'rgb= 0.01))
(ensure-same rgb (parse-hex-rgb "123456") :test (rcurry #'rgb= 0.01))
(ensure-same rgb (parse-hex-rgb "#135") :test (rcurry #'rgb= 0.01))
(ensure-same rgb (parse-hex-rgb "135") :test (rcurry #'rgb= 0.01))
(flet ((aux (list1 list2)
(and (rgb= (car list1) (car list2) 0.01)
(eps= (cadr list1) (cadr list2) 0.01))))
(ensure-same (list rgb 0.47) (multiple-value-list (parse-hex-rgb "#12345678")) :test #'aux)
(ensure-same (list rgb 0.47) (multiple-value-list (parse-hex-rgb "12345678")) :test #'aux)
(ensure-same (list rgb 0.47) (multiple-value-list (parse-hex-rgb "#1357")) :test #'aux)
(ensure-same (list rgb 0.47) (multiple-value-list (parse-hex-rgb "1357")) :test #'aux))))
(addtest (cl-colors-tests)
print-hex-rgb/format
(ensure-same "#123456" (with-output-to-string (*standard-output*)
(print-hex-rgb (rgb 0.070 0.203 0.337)
:destination T))))
(addtest (cl-colors-tests)
hex<->rgb
(loop repeat 100 do
(let ((rgb (random-rgb)))
(ensure-same rgb (parse-hex-rgb (print-hex-rgb rgb)) :test (rcurry #'rgb= 0.01)))))
(addtest (cl-colors-tests)
parse-hex-rgb-ranges
(ensure-same (rgb 0.070 0.203 0.337) (parse-hex-rgb "foo#123456zzz" :start 3 :end 10)
:test (rcurry #'rgb= 0.001)))

View file

@ -0,0 +1,14 @@
CL-EMB has fixes by current maintainer, Michael Raskin <38a938c2@rambler.ru>
This fixes are Copyright (c) 2009 by Moscow Center of Continious
Mathematical Education.
CL-EMB is written and Copyright (c) 2004, 2005, 2006 by Stefan Scholl.
Parts of the source are taken from LSP, written by
John Wiseman and copyright 2001, 2002 I/NET Inc.
See lsp-LICENSE.txt
CL-EMB is licensed under the terms of the Lisp Lesser GNU
Public License (http://opensource.franz.com/preamble.html), known as
the LLGPL. The LLGPL consists of a preamble (see above URL) and the
LGPL. Where these conflict, the preamble takes precedence.
CL-EMB is referenced in the preamble as the "LIBRARY."

View file

@ -0,0 +1,339 @@
# cl-emb: Embedded Common Lisp
A mixture of features from eRuby and HTML::Template. You could name it "Yet
Another LSP" (LispServer Pages) but it's a bit more than that and not limited to
a certain server or text format.
This is a mirror of http://mtn-host.prjek.net/projects/cl-emb
The primary development repository is in Monotone, this repository will receive
just the automated snapshots.
# License
[LLGPL](http://opensource.franz.com/preamble.html)
# Installing
```lisp
(ql:quickload :cl-emb)
```
CL-EMB can also be installed manually with [ASDF-INSTALL](http://weitz.de/asdf-install/).
# Usage
## [generic function] `EXECUTE-EMB name &key env generator-maker => string`
`NAME` can be a registered (with `REGISTER-EMB`) emb code or a pathname (type
`PATHNAME`) of a file containing the code. Returns a string. Keyword parameter
ENV to pass objects to the code. `ENV` must be a plist. `ENV` can be accessed
within your emb code. The `GENERATOR-MAKER` is a function which gets called
with a key and value from the given `ENV` and should return a generator function
like described
[here](http://www.cs.northwestern.edu/academics/courses/325/readings/graham/generators.html).
## [generic function] `REGISTER-EMB name code => emb-function`
Internally registeres given `CODE` with `NAME` to be called with
`EXECUTE-EMB`. `CODE` can be a string or a pathname (type `PATHNAME`) of a file
containing the code.
## [function] `PPRINT-EMB-FUNCTION name`
`DEBUG` function. Pretty prints function form, if `*DEBUG*` was `T` when the
function was registered.
## [function] `CLEAR-EMB name`
Remove named emb code.
## [function] `CLEAR-EMB-ALL`
Remove all registered emb code.
## [function] `CLEAR-EMB-ALL-FILES`
Remove all registered file emb code (registered/executed by a pathname).
## [special variable] `*EMB-START-MARKER*` (default `"<%"`)
Start of scriptlet or expression. Remember that a following `#\=` indicates an
expression.
## [special variable] `*EMB-END-MARKER*` (default `"%>"`)
End of scriptlet or expression.
## [special variable] `*ESCAPE-TYPE*`
Default value for escaping `@var` output is `:RAW` Can be changed to `:XML`,
`:HTML`, `:URI`, `:URL`, `:URL-ENCODE`, `:LATEX`.
## [special variable] `*FUNCTION-PACKAGE*`
Package the emb function body gets interned to.
Default: `(find-package :cl-emb-intern)`.
## [special variable] `*DEBUG*`
Debugging mode if `T`. Default: `NIL`.
## [special variable] `*LOCKING-FUNCTION*`
Function to call to lock access to an internal hash table. Must accept a
function designator which must be called with the lock hold.
**IMPORTANT:** The locking function must return the value of the function it
calls!
Example:
```lisp
(defvar *emb-lock* (kmrcl::make-lock "emb-lock")
"Lock for CL-EMB.")
(defun emb-lock-function (func)
"Lock function for CL-EMB."
(kmrcl::with-lock-held (*emb-lock*)
(funcall func)))
(setf emb:*locking-function* 'emb-lock-function)
```
Files get cached and reread when they change.
The emb code consists of normal text (HTML, XML, or any other text format) and
special tags you know from eRuby or JSP (JavaServer Pages) which can hold Common
Lisp or CL-EMB's template tags, perhaps comparable to JSP's taglib.
- `<% ... %>` is a scriptlet tag, and wraps Common Lisp code.
- `<%= ... %>` is an expression tag. Its content gets evaluated and fed as a
parameter to `(FORMAT T "~A" ...)`.
- `<%# ... #%>` is a comment. Everything within will be removed/ignored. Can't
be nested!
## Examples
```lisp
CL-USER> (asdf:oos 'asdf:load-op :cl-emb)
CL-USER> (cl-emb:register-emb "test1"
"10 stars: <% (dotimes (i 10) %>*<% ) %>")
#<CL-EMB::EMB-FUNCTION {9B74259}>
CL-USER> (cl-emb:execute-emb "test1")
"10 stars: **********"
CL-USER> (cl-emb:register-emb "test2" "2 + 2 = <%= (+ 2 2) %>")
#<CL-EMB::EMB-FUNCTION {9BCACE1}>
CL-USER> (cl-emb:execute-emb "test2")
"2 + 2 = 4"
CL-USER> (let ((emb:*emb-start-marker* "<?emb")
(emb:*emb-end-marker* "?>"))
(emb:register-emb "marker-test"
"42 + 42 = <?emb= (+ 42 42) ?>"))
#<CL-EMB::EMB-FUNCTION {97BEFD9}>
CL-USER> (emb:execute-emb "marker-test")
"42 + 42 = 84"
```
# Template Tags
You can use special template tags instead of Common Lisp code between `<%` and
`%>`. This will be translated to Common Lisp and serves as a simple shortcut for
you.
And more important: It's easier to use for non-programmers. A designer can work
on HTML code and insert these simple template tags.
Template tags start with `@`.
Currently supported: `@if`, `@else`, `@endif`, `@ifnotempty`, `@unless`,
`@endunless`, `@var`, `@repeat`, `@endrepeat`, `@loop`, `@endloop`, `@include`,
`@includevar`, `@call`, `@with`, `@endwith`, `@set`, `@genloop`, `@endgenloop`,
`@insert`.
`@if` and `@unless` check if the given parameter is set in the supplied
environment (parameter ENV of `EXECUTE-EMB`). The environment is a plist with
keyword + value pairs. Must be terminated with `@endif` or `@endunless`.
`@ifnotempty` works like `@if` but considers the empty string false.
`@ifequal` accepts two parameters interpreted as variable names. It works like
`@if` but checks whether the values of two variables are equal. Variable names
are intepreted as in `@var`.
Note that `@ifnotempty` and `@ifequal` are supposed to be used together with
`@else` and `@endif`.
`@var` emits the corresponding value from the environment. Uses the escape type
defined in `*ESCAPE-TYPE*` (Default `:raw`, no escaping) or with -escape
modifier. E.g. `<% @var foo -escape xml %>` or without modifier `<% @var foo
%>` Supported escaping: `raw`, `xml` (aka `html`), `uri` (aka `url` or
`url-encode`), `latex`.
`@insert` inserts a given (text) file. Parameter from the environment. E.g. `<%
@insert textfile %>`.
`@repeat` repeats everything between it and `@endrepeat` the given
times. Parameter can be a number or a name. The name will be used to lookup the
corresponding value from the environment.
`@loop` loops over a named list in the environment. Environment gets set to
current plist inside this list. Must be terminated with `@endloop`.
`@include` includes a given file. Relative to current template. `@includevar`
does the same, but the parameter is treated like a variable name containing the
path to the file. Variable name is treated like in `@var`.
`@call` calls a given emb-function, which was registered with `REGISTER-EMB`.
`@with` is similar to `@loop` as it sets the current environment to the named
plist. `@loop` needs a list of plists and `@with` just a plist associated to the
given name. Block ends in `@endwith`.
`@set` is used to set special variables like `*ESCAPE-TYPE*` from within a emb
code. This way a default for a file can be specified in the file itself. The
variables are changed for the current and called/included code. Changes to the
variables in called/included code don't effect the caller/ includer. E.g. `<%
@set escape=uri %>`. Currently supported: `escape` (`raw`, `xml`, `html`, `url`,
`uri`, `url-encode`, `latex`).
`@genloop` starts a special kind of loop: a generator loop. It must be
terminated by `@endgenloop` and operates on a generator returned by the given
`GENERATOR-MAKER` (see `EXECUTE-EMB`). The `GENERATOR-MAKER` gets called with
two parameters: the key (which is the argument to `@genloop`) and the
corresponding value in the plist. Each time in the loop the generator is called
first with the parameter `:TEST` to see if there's data left. The generator
must return a plist on `:NEXT`, which will be the current `ENV` (like `@with` or
within a normal `@loop`).
The parameters which access the environment can just be the name of a keyword
symbol in the plist. `foo` -> :FOO in `(:FOO "bar")` Or you can provide a path
within a nested plist structure by dividing the parts of the path with a
slash. `foo/bar` -> Value of `:BAR` inside the plist at `:FOO`. `(:FOO (:BAR
"yeah"))` -> `"yeah"` Starting the parameter with a slash lets it traverse the
nested plists from the top. That way you can access top values inside loops.
Writing `<% @var foo/bar/quux %>` can be translated to `(GETF (GETF (GETF ENV
:FOO) :BAR) :QUUX)`.
## Examples
```lisp
CL-USER> (cl-emb:register-emb "test1"
"Foo: <% @if foo %>Yes!<% @else %>No!<% @endif %>")
#<CL-EMB::EMB-FUNCTION {9C0F2D1}>
CL-USER> (cl-emb:execute-emb "test1" :env '(:foo t))
"Foo: Yes!"
CL-USER> (cl-emb:execute-emb "test1")
"Foo: No!"
CL-USER> (cl-emb:execute-emb "test1" :env '(:foo nil))
"Foo: No!"
CL-USER> (cl-emb:register-emb "test2"
"What is set? -> <% @call test1 %>")
#<CL-EMB::EMB-FUNCTION {9C526E9}>
CL-USER> (cl-emb:execute-emb "test2" :env '(:foo t))
"What is set? -> Foo: Yes!"
CL-USER> (cl-emb:register-emb "test3"
"10 stars: <% @repeat 10 %>*<% @endrepeat %>")
#<CL-EMB::EMB-FUNCTION {9C9F1D1}>
CL-USER> (cl-emb:execute-emb "test3")
"10 stars: **********"
CL-USER> (cl-emb:register-emb "test4"
"<% @loop numbers %>[<% @var de %>,<% @var en %>]<% @endloop %>")
#<CL-EMB::EMB-FUNCTION {9174DF1}>
CL-USER> (cl-emb:execute-emb "test4"
:env '(:numbers ((:de "EINS" :en "ONE")
(:de "ZWEI" :en "TWO"))))
"[EINS,ONE][ZWEI,TWO]"
CL-USER> (emb:register-emb "test5"
"<a href=\"http://somewhere.test/test.cgi?<% @var foo -escape uri %>\"><% @var foo %></a>")
#<CL-EMB::EMB-FUNCTION {9FBF5F1}>
CL-USER> (let ((emb:*escape-type* :html))
(emb:execute-emb "test5" :env '(:foo "10 > 7")))
"<a href=\"http://somewhere.test/test.cgi?10+%3E+7\">10 &gt; 7</a>"
CL-USER> (emb:register-emb "test6" "1. <% @with one %>BAZ: <% @var baz %><% @endwith%>
2. <% @with two %>BAZ: <% @var baz %><% @endwith%>")
#<CL-EMB::EMB-FUNCTION {9916EB1}>
CL-USER> (emb:execute-emb "test6" :env '(:one (:baz "first")
:two (:baz "second")))
"1. BAZ: first
2. BAZ: second"
CL-USER> (emb:register-emb "test7" " - <% @var foo -escape uri %> - ")
#<CL-EMB::EMB-FUNCTION {96F1239}>
CL-USER> (emb:pprint-emb-function "test7")
(LAMBDA (&OPTIONAL CL-EMB-INTERN::ENV)
(WITH-OUTPUT-TO-STRING (*STANDARD-OUTPUT*)
(PROGN
(WRITE-STRING " - ")
(FORMAT T "~A" (CL-EMB::ECHO (GETF CL-EMB-INTERN::ENV :FOO) :ESCAPE :URI))
(WRITE-STRING " - "))))
; No value
CL-USER> (emb:register-emb "test8" "<% @set escape=xml %>--<% @var hey %>--")
#<CL-EMB::EMB-FUNCTION {962B839}>
CL-USER> (emb:register-emb "test9" "--<% @var hey %>--<% @call test8 %>--<% @var hey %>--")
#<CL-EMB::EMB-FUNCTION {96931A9}>
CL-USER> (emb:execute-emb "test9" :env '(:hey "5>2"))
"--5>2----5&gt;2----5>2--"
CL-USER> (emb:register-emb "test10" "Square root from 1 to <% @var numbers %>: <% @genloop numbers %>sqrt(<% @var number %>) = <% @var sqrt %> <% @endgenloop %>")
#<CL-EMB::EMB-FUNCTION {581EC765}>
CL-USER> (defun make-sqrt-1-to-n-gen (key n)
(declare (ignore key))
(let ((i 1))
#'(lambda (cmd)
(ecase cmd
(:test (> i n))
(:get `(:number ,i :sqrt ,(sqrt i)))
(:next (prog1 `(:number ,i :sqrt ,(sqrt i))
(unless (> i n)
(incf i))))))))
MAKE-SQRT-1-TO-N-GEN
CL-USER> (emb:execute-emb "test10" :env '(:numbers 10) :generator-maker 'make-sqrt-1-to-n-gen)
"Square root from 1 to 10: sqrt(1) = 1.0 sqrt(2) = 1.4142135 sqrt(3) = 1.7320508 sqrt(4) = 2.0 sqrt(5) = 2.236068 sqrt(6) = 2.4494898 sqrt(7) = 2.6457512 sqrt(8) = 2.828427 sqrt(9) = 3.0 sqrt(10) = 3.1622777 "
CL-USER> (emb:register-emb "test11" "<% @loop bands %>Band: <% @var band %> (Genre: <% @var /genre %>)<br><% @endloop %>")
#<CL-EMB::EMB-FUNCTION {58ADB12D}>
CL-USER> (emb:execute-emb "test11" :env '(:genre "Rock" :bands ((:band "Queen") (:band "The Rolling Stones") (:band "ZZ Top"))))
"Band: Queen (Genre: Rock)<br>Band: The Rolling Stones (Genre: Rock)<br>Band: ZZ Top (Genre: Rock)<br>"
CL-USER> (emb:register-emb "test12" "<% @repeat /foo/bar/count %>*<% @endrepeat %>")
#<CL-EMB::EMB-FUNCTION {58B7583D}>
CL-USER> (emb:execute-emb "test12" :env '(:foo (:bar (:count 42))))
"******************************************"
CL-USER> (emb:register-emb "test13" "The file:<pre><% @insert textfile %></pre>")
#<CL-EMB::EMB-FUNCTION {5894326D}>
CL-USER> (emb:execute-emb "test13" :env '(:textfile "/etc/gentoo-release"))
"The file:<pre>Gentoo Base System version 1.6.14
</pre>"
```
# Credits
Uses code from John Wiseman. See http://lemonodor.com/archives/000128.html and
lsp-LICENSE.txt Thanks to Edi Weitz for letting me use his code for
`ESCAPE-FOR-XML`.
Thanks to Eitarow Fukamachi for the whitespace-trimming patch.
Thanks to Christoph Finkensiep for making `getf*` a generic function.
# Author
Stefan Scholl <stesch@no-spoon.de>
# Current Maintainer
Michael Raskin <38a938c2@rambler.ru>

View file

@ -0,0 +1,14 @@
- Documentation
- More examples
- Tests
- Writing own escape functions?
- Better error handling
- Examples for generator loop in the examples.html
- Export GETF-EMB

View file

@ -0,0 +1,26 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; This software is Copyright (c) Stefan Scholl, 2004.
;;; Stefan Scholl grants you the rights to distribute
;;; and use this software as governed by the terms
;;; of the Lisp Lesser GNU Public License
;;; (http://opensource.franz.com/preamble.html),
;;; known as the LLGPL.
(in-package #:cl-user)
(defpackage #:cl-emb.system
(:use #:cl
#:asdf))
(in-package #:cl-emb.system)
(defsystem #:cl-emb
:version "0.4.3"
:author "Stefan Scholl <stesch@no-spoon.de>"
:licence "Lesser Lisp General Public License"
:description "A templating system for Common Lisp"
:depends-on (#:cl-ppcre)
:components ((:file "packages")
(:file "emb" :depends-on ("packages"))))

View file

@ -0,0 +1,523 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; This file contains some fixes by Michael Raskin
;;; They are Copyright (c) Moscow Center of Continious Mathematical
;;; Education, 2009
;;; This software is Copyright (c) Stefan Scholl, 2004.
;;; Stefan Scholl grants you the rights to distribute
;;; and use this software as governed by the terms
;;; of the Lisp Lesser GNU Public License
;;; (http://opensource.franz.com/preamble.html),
;;; known as the LLGPL.
;;; Parts of the source are taken from LSP, written by
;;; John Wiseman and copyright 2001, 2002 I/NET Inc.
;;; (http://www.inetmi.com/)
;;; See lsp-LICENSE.txt
(in-package :cl-emb)
(defpackage :cl-emb-intern (:use :cl))
(defvar *function-package* (find-package :cl-emb-intern)
"Package the emb function body gets interned to.")
(defvar *debug* nil
"Debugging for CL-EMB.")
(defvar *locking-function* nil
"Function to call to lock access to an internal hash table. Must accept
a function designator which must be called with the lock hold.")
(defmacro with-lock (&body body)
"Locking all accesses to *functions*"
`(cond (*locking-function*
(funcall *locking-function* #'(lambda () ,@body)))
(t ,@body)))
(defgeneric execute-emb (name &key env generator-maker)
(:documentation "Execute named emb code. Returns a string. Keyword parameter ENV
to pass objects to the code. ENV must be a plist."))
(defmethod execute-emb ((name t) &key env generator-maker)
(funcall (get-emb-function name) :env env :generator-maker generator-maker :name name))
(defmethod execute-emb ((name pathname) &key env generator-maker)
(let ((fun (or (get-emb-function name)
(emb-function-function (register-emb name name)))))
(funcall fun :env env :generator-maker generator-maker :name name)))
(defvar *functions* (make-hash-table :test #'equal)
"Table mapping names to emb-function instances.")
(defclass emb-function ()
((path :initarg :path
:accessor emb-function-path)
(time :initarg :time
:accessor emb-function-time)
(function :initarg :function
:accessor emb-function-function)
(form :initarg :form
:initform nil
:accessor emb-function-form)))
(defun make-emb-function (path time function &optional form)
"Constructor for class EMB-FUNCTION."
(make-instance 'emb-function
:path path
:time time
:function function
:form form))
(defun pprint-emb-function (name)
"DEBUG function. Pretty prints function form, if *DEBUG* was t
when the function was registered."
(with-lock
(pprint (emb-function-form (gethash name *functions*)))))
(defun clear-emb-all ()
"Remove all registered emb code."
(with-lock
(clrhash *functions*)))
(defun clear-emb (name)
"Remove named emb code."
(with-lock
(remhash name *functions*)))
(defun clear-emb-all-files ()
"Remove all registered file emb code (registered/executed by a pathname)."
(with-lock
(maphash (lambda (key value) (declare (ignore value))
(when (typep key 'pathname) (remhash key *functions*)))
*functions*)))
(defun get-emb-function (name)
"Returns the named function implementing a registered emb code.
Rebuilds it when text template was a file which has been modified."
(with-lock
(let* ((emb-function (gethash name *functions*))
(path (when emb-function (emb-function-path emb-function))))
(cond ((and (not (typep name 'pathname)) (null emb-function))
(error "Function ~S not found." name))
((null emb-function)
(return-from get-emb-function))
((and path
(> (file-write-date path) (emb-function-time emb-function)))
;; Update when file is newer
(multiple-value-bind (function form)
(construct-emb-function (contents-of-file path))
(setf (emb-function-time emb-function) (file-write-date path)
(emb-function-function emb-function) function
(emb-function-form emb-function) form))))
(emb-function-function emb-function))))
(defgeneric register-emb (name code)
(:documentation "Register given CODE as NAME."))
(defmethod register-emb (name (code pathname))
(multiple-value-bind (function form)
(construct-emb-function (contents-of-file code))
(with-lock
(setf (gethash name *functions*)
(make-emb-function code
(file-write-date code)
function
form)))))
(defmethod register-emb (name (code string))
(multiple-value-bind (function form)
(construct-emb-function code)
(with-lock
(setf (gethash name *functions*)
(make-emb-function nil
(get-universal-time)
function
form)))))
(defvar *emb-start-marker* "<%"
"Start of scriptlet or expression. Remember that a following #\=
indicates an expression.")
(defvar *emb-end-marker* "%>"
"End of scriptlet or expression.")
(defparameter *set-special-list*
'(("escape" . "cl-emb:*escape-type*")
("case-sensitivity" . "cl-emb:*case-sensitivity*")))
(defparameter *set-parameter-list*
'(("xml" . ":xml")
("html" . ":html")
("url" . ":url")
("uri" . ":uri")
("url-encode" . ":url-encode")
("raw" . ":raw")
("latex" . ":latex")
("t" . "t")
("nil" . "nil")))
;; TODO: Refactor! Looks a bit clumsy.
(defun set-specials (match &rest registers)
"Parse parameter(s) of @set and set special variables
like e. g. *ESCAPE-TYPE*."
;; <% @set escape=xml schnuffel=poe %>
(declare (ignore match))
(let ((setf-pairs
(let ((setf-list nil))
(dolist (pair (cl-ppcre:split "\\s+" (first registers))
(when (first setf-list)
(format nil "~{ ~A~}" (reverse setf-list))))
(destructuring-bind (left right)
(cl-ppcre:split "=" pair)
(let ((place (rest (assoc left *set-special-list* :test #'equalp)))
(value (rest (assoc right *set-parameter-list* :test #'equalp))))
(when (and place value)
(push (concatenate 'string place " " value) setf-list))))))))
(if setf-pairs
(format nil "(setf ~A)" setf-pairs)
"")))
(defparameter *template-tag-expand*
`(("\\s+@if\\s+(\\S+)\\s*" . " (cond ((cl-emb::autofuncall (cl-emb::getf-emb \"\\1\")) ")
("\\s+@ifnotempty\\s+(\\S+)\\s*" . " (cond ((let* ((value (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\")))) (or (numberp value) (> (length value) 0))) ")
("\\s+@ifequal\\s+(\\S+)\\s+(\\S+)\\s*" . " (cond ((equal (format nil \"~a\" (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\"))) (format nil \"~a\" (cl-emb::autofuncall (cl-emb::getf-emb \"\\2\")))) ")
("\\s+@else\\s*" . " ) (t ")
("\\s+@endif\\s*" . " )) ")
("\\s+@unless\\s+(\\S+)\\s*" . " (cond ((not (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\"))) ")
("\\s+@endunless\\s*" . " )) ")
("=?\\s+@var\\s+(\\S+)\\s+-(\\S+)\\s+(\\S+)\\s*"
. "= (cl-emb::echo (cl-emb::getf-emb \"\\1\") :\\2 :\\3) ")
("=?\\s+@var\\s+(\\S+)\\s*" . "= (cl-emb::echo (cl-emb::getf-emb \"\\1\")) ")
("\\s+@repeat\\s+(\\d+)\\s*" . " (dotimes (i \\1) ")
("\\s+@repeat\\s+(\\S+)\\s*" . " (dotimes (i (or (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\")) 0)) ")
("\\s+@endrepeat\\s*" . " ) ")
("\\s+@loop\\s+(\\S+)\\s*" . " (dolist (env (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\"))) ")
("\\s+@endloop\\s*" . " ) ")
("\\s+@genloop\\s+(\\S+)\\s*" . " (let ((env)
(%gen (funcall generator-maker :\\1
(cl-emb::getf-emb \"\\1\"))))
(loop
(when (funcall %gen :test) (return))
(setq env (funcall %gen :next))
(progn ")
("\\s+@endgenloop\\s*" . " ))) ")
("\\s+@with\\s+(\\S+)\\s*" . " (let ((env (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\")))) ")
("\\s+@endwith\\s*" . " ) ")
("\\s+@include\\s+(\\S+)\\s*" . "= (let ((cl-emb:*escape-type* cl-emb:*escape-type*))
(cl-emb:execute-emb (merge-pathnames \"\\1\" template-path-default) :env env :generator-maker generator-maker)) ")
("\\s+@includevar\\s+(\\S+)\\s*" . "= (let* ((cl-emb:*escape-type* cl-emb:*escape-type*)
(parameter (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\"))))
(unless parameter (error \"use of @includevar on undefined parameter ~s\" \"\\1\"))
(cl-emb:execute-emb (merge-pathnames parameter template-path-default) :env env :generator-maker generator-maker)) ")
("\\s+@call\\s+(\\S+)\\s*" . "= (let ((cl-emb:*escape-type* cl-emb:*escape-type*))
(cl-emb:execute-emb \"\\1\" :env env :generator-maker generator-maker)) ")
("\\s+@insert\\s+(\\S+)\\s*" . "= (cl-emb::contents-of-file (merge-pathnames (cl-emb::autofuncall (cl-emb::getf-emb \"\\1\")) template-path-default)) ")
("\\s+@set\\s+(.*?)\\s*" . ,(function set-specials))
("#.*" . "")
)
"List of conses. FIRST is regex, REST replacement (STRING or FUNCTION).
Functions get called with two parameters: match and list of registers.")
;; Code from Edi Weitz's TBNL <http://weitz.de/tbnl/>
(defun escape-for-xml (string)
(with-output-to-string (out)
(with-input-from-string (in string)
(loop for char = (read-char in nil nil)
while char
do (case char
((#\<) (write-string "&lt;" out))
((#\>) (write-string "&gt;" out))
((#\") (write-string "&quot;" out))
((#\') (write-string "&#39;" out))
((#\&) (write-string "&amp;" out))
(otherwise (write-char char out)))))))
(defun escape-by-table (string replacements)
(with-output-to-string (out)
(with-input-from-string (in string)
(loop for char = (read-char in nil nil)
while char
do (let ((new (find char replacements
:test 'equal
:key 'car)))
(if new
(write-string (cdr new) out)
(write-char char out))
)))))
(defvar *latex-replacements*)
(setf *latex-replacements*
(mapcar
(lambda (x) `(,(character (car x)) . ,(cdr x)))
`(
("#" . "\\#")
("$" . "\\$")
("%" . "\\%")
("&" . "\\&")
("_" . "\\_")
("{" . "\\{")
("}" . "\\}")
("<" . "{$<$}")
(">" . "{$>$}")
("\\" . "{$\\backslash{}$}")
("|" . "{$\\vert{}$}")
("~" . "{\\,$\\tilde{}$\\,}")
("^" . "{\\,$\\hat{}$\\,}")
(,(string #\Return) . "~\\\\")
(,(string #\NewLine) . "~\\\\")
("\"" . "{'{}'}")
(,(string (code-char 173)) . "\\-") ; Soft hyphen
(,(string (code-char 160)) . "~") ; No-break space
(,(string (code-char 8209)) . "-") ; Non-breaking hyphen
(,(string (code-char 8211)) . "--") ; En-dash
(,(string (code-char 8212)) . "---") ; Em-dash
(,(string (code-char 8470)) . "{\\textnumero}") ; Number sign
)))
(defun escape-for-latex (string)
(escape-by-table string
*latex-replacements*))
;; Inspired by Edi Weitz' ESCAPE-FOR-HTML
(defun url-encode (string)
"URL-encode a string."
(with-output-to-string (out)
(with-input-from-string (in string)
(loop for char = (read-char in nil nil)
while char
if (find char "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.")
do (write-char char out)
else if (char= char #\Space)
do (write-char #\+ out)
else
do (format out "%~2,'0x" (char-code char))))))
(defvar *case-sensitivity* nil
"Whether use case-sensitive mode (the default) or case-insensitive mode. If this is set NIL, the case of keys in ENV will be ignored.")
(defun string-to-keyword (string)
"Interns a given STRING uppercased in the keyword package."
(nth-value 0 (intern
(if *case-sensitivity*
string
(string-upcase string)) :keyword)))
(defgeneric getf* (thing key &optional default)
(:documentation "Returns a value by a key"))
(defmethod getf* ((plist list) key &optional default)
"Uses getf to get a value from a plist"
(if *case-sensitivity*
(getf plist key default)
(loop for (k v) on plist by #'cddr
when (string-equal k key)
do (return v)
finally (return default))))
(defmethod getf* ((table hash-table) key &optional default)
"Uses gethash to get a value from a hash-table"
(gethash key table default))
(defmethod getf* ((object standard-object) key &optional default)
"Uses slot-value to get a value from a standard object, where the slot name is derived from key"
(let ((slot-name (intern (princ-to-string key)
(symbol-package (class-name (class-of object))))))
(if (and (slot-exists-p object slot-name)
(slot-boundp object slot-name))
(slot-value object slot-name)
default)))
(defmacro getf-emb (key)
"Search either plist TOPENV or ENV according to the search path in KEY. KEY
is a string."
(let ((plist (if (char= (char key 0) #\/)
(find-symbol "TOPENV" emb:*function-package*)
(find-symbol "ENV" emb:*function-package*)))
(path-parts (cl-ppcre:split "/" key :sharedp t)))
(labels ((dig-plist (plist keys)
(if (null keys)
plist
(dig-plist
(if (zerop (length (first keys)))
plist
`(getf* ,plist ,(string-to-keyword (first keys))))
(rest keys)))))
(dig-plist plist path-parts))))
(defvar *escape-type* :raw
"Default value for escaping @var output.")
(defun autofuncall (v)
(if (functionp v)
(autofuncall (funcall v))
v))
(defun echo (string &key (escape *escape-type*))
"Emit given STRING. Escape if wanted (global or via ESCAPE keyword).
STRING can be NIL."
(let ((str (cond
((stringp string) string)
((null string) "")
((functionp string)
(format nil "~a" (or (autofuncall string) "")))
(t (format nil "~a" string))
)))
(case escape
((:html :xml)
(escape-for-xml str))
((:latex)
(escape-for-latex str))
((:url :uri :url-encode)
(url-encode str))
(otherwise ; incl. :raw
str))))
(defun insert-file (filename)
"Get given file FILENAME."
(contents-of-file filename))
(let ((scanner-hash (make-hash-table :test #'equal)))
(defun scanner-for-expand-template-tag (tag)
"Returns a CL-PPCRE scanner which matches a template tag expanded by EXPAND-TEMPLATE-TAGS.
Scanners are memoized in SCANNER-HASH once they are created."
(or (gethash tag scanner-hash)
(setf (gethash tag scanner-hash)
(ppcre:create-scanner tag))))
(defun clear-expand-template-tag-hash ()
"Removes all scanners for template tags from cache."
(clrhash scanner-hash)))
(defun expand-template-tags (string)
"Expand template-tags (@if, @else, ...) to Common Lisp.
Replacement and regex in *TEMPLATE-TAG-EXPAND*"
(labels ((expand-tags (string &optional (expands *template-tag-expand*))
(let ((regex (scanner-for-expand-template-tag
(concatenate 'string "(?is)"
"^" (first (first expands)) "$")))
(replacement (rest (first expands))))
(if (null (rest expands))
(ppcre:regex-replace-all regex string replacement :simple-calls t)
(expand-tags
(ppcre:regex-replace-all regex string replacement :simple-calls t)
(rest expands))))))
(ppcre:regex-replace-all (format nil "(?is)(~A\\-?)(.+?)(\\-?~A)"
(ppcre:quote-meta-chars *emb-start-marker*)
(ppcre:quote-meta-chars *emb-end-marker*))
string
(lambda (match start-tag string end-tag)
(declare (ignore match))
(if (ppcre:scan "(?is)^#.+#$" string)
""
(concatenate 'string
start-tag
(expand-tags string)
end-tag)))
:simple-calls t)))
(defvar *emb-stream-redirection* "with-output-to-string (*standard-output*)")
(defun construct-emb-function (code)
"Builds and compiles the emb-function out of template code."
(let ((form
`,(let ((*package* *function-package*))
(read-from-string
(format nil "(lambda (&key env generator-maker name)(declare (ignorable env generator-maker))
(let ((topenv env)
(template-path-default (if (typep name 'pathname) name *default-pathname-defaults*)))
(declare (ignorable topenv template-path-default))
(~a
(progn ~A))))"
*emb-stream-redirection*
(construct-emb-body-string
(expand-template-tags code)))))))
(values (compile nil form)
(when *debug* form))))
(defun contents-of-file (pathname)
"Returns a string with the entire contents of the specified file."
(with-open-file (in pathname :direction :input)
;; See http://www.emmett.ca/~sabet/licensets/slurp.html
(let* ((file-length (file-length in))
(seq (make-string file-length))
(pos (read-sequence seq in)))
(if (< pos file-length)
(subseq seq 0 pos)
seq))))
(defun string-right-trim-spaces-until-newline (string)
(remove #\Newline (string-right-trim '(#\Space #\Tab) string)
:from-end t
:count 1))
;; (i) Converts text outside <% ... %> tags into calls
;; to WRITE-STRING, (ii) Text inside <% ... %>
;; ("scriptlets") is straight lisp code, (iii) Text inside <%= ... %>
;; ("expressions") becomes the argument to (FORMAT t "~A" ...)
;; The markers <% and %> can be overridden by setting
;; *emb-start-marker* and *emb-end-marker*
(defun construct-emb-body-string (code &optional (start 0))
"Takes a string containing an emb code and returns a string
containing the lisp code that implements that emb code."
(multiple-value-bind (start-tag start-code tag-type trim-start-whitespaces)
(next-code code start)
(if (not start-tag)
(format nil "(write-string ~S)" (subseq code start))
(let* ((end-code (search *emb-end-marker* code :start2 start-code))
(trim-end-whitespaces (char= (char code (1- end-code)) #\-)))
(if (not end-code)
(error "EOF reached in EMB inside open '~A' tag." *emb-start-marker*)
(format nil "(write-string ~S) ~A ~A"
(if trim-start-whitespaces
(string-right-trim-spaces-until-newline (subseq code start start-tag))
(subseq code start start-tag))
(format nil (tag-template tag-type)
(subseq code start-code (if trim-end-whitespaces
(1- end-code)
end-code)))
(construct-emb-body-string
code
(if trim-end-whitespaces
(let ((next-pos (cl-ppcre:scan "(?:\\S|\\n)" code :start (+ end-code (length *emb-end-marker*)))))
(cond
((null next-pos) (length code))
((char= (elt code next-pos) #\Newline)
(1+ next-pos))
(t next-pos)))
(+ end-code (length *emb-end-marker*))))))))))
;; Finds the next scriptlet or expression tag in EMB source. Returns
;; nil if none are found, otherwise returns 3 values:
;; 1. The position of the first character of the start tag.
;; 2. The position of the contents of the tag.
;; 3. The type of tag (:scriptlet or :expression).
;; 4. Whether trim whitespaces before the start tag.
(defun next-code (string start)
(let ((start-tag (search *emb-start-marker* string :start2 start)))
(if (not start-tag)
nil
(let ((start-code (+ start-tag (length *emb-start-marker*))))
(case (and (> (length string) start-code)
(char string start-code))
(#\= (values start-tag (1+ start-code) :expression nil))
(#\- (values start-tag (1+ start-code) :scriptlet t))
(otherwise (values start-tag start-code :scriptlet nil)))))))
;; Given a tag type (:scriptlet or :expression), returns a format
;; string to be used to generate source code from the contents of the
;; tag.
(defun tag-template (tag-type)
(ecase tag-type
((:scriptlet) "~A")
((:expression) "(format t \"~~A\" ~A)")))

View file

@ -0,0 +1,18 @@
body { font-family: sans-serif;
background-color: #fff;
color: #000; }
pre { margin-top: 0;
margin-bottom: 0; }
table { width: 100%; }
th, td { text-align: left;
vertical-align: top; }
th { background-color: #eee; }
caption { font-weight: bold; }
table, ol { margin-bottom: 2em; }

View file

@ -0,0 +1,232 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link href="examples.css" rel="stylesheet" type="text/css" />
<title>CL-EMB: Examples</title>
</head>
<body>
<h1>Some examples of <a href="http://common-lisp.net/project/cl-emb/">CL-EMB</a> usage</h1>
<ol>
<li><a href="#combine-cl-who">Combining CL-EMB with CL-WHO</a></li>
<li><a href="#simple-loop">A simple loop</a></li>
<li><a href="#build-dropdown">Build a dropdown</a></li>
<li><a href="#mark-fields">Mark invalid form fields</a></li>
<li><a href="#using-generic-templates">Using generic templates</a></li>
</ol>
<table border="1" cellpadding="2" id="combine-cl-who">
<caption>Combining CL-EMB with CL-WHO</caption>
<tr>
<th>
Description
</th>
<td>
You can mix several methods of HTML generating together. Think of <a href="http://www.cliki.net/Lisp%20Markup%20Languages">Lisp Markup Languages</a> like <a href="http://weitz.de/cl-who/">CL-WHO</a>. <small>(Example code from the <a href="http://weitz.de/cl-who/">CL-WHO</a> documentation.)</small>
</td>
</tr>
<tr>
<th>
ENV
</th>
<td>
<code>NIL</code>
</td>
</tr>
<tr>
<th>
Dependencies
</th>
<td>
<a href="http://weitz.de/cl-who/">CL-WHO</a>
</td>
</tr>
<tr>
<td colspan="2">
<pre>&lt;h1&gt;Music links&lt;/h1&gt;
&lt;%
(cl-who:with-html-output (*standard-output*)
(loop for (link . title) in
'((&quot;http://zappa.com/&quot; . &quot;Frank Zappa&quot;)
(&quot;http://marcusmiller.com/&quot; . &quot;Marcus Miller&quot;)
(&quot;http://www.milesdavis.com/&quot; . &quot;Miles Davis&quot;))
do (cl-who:htm (:a :href link
(:b (cl-who:str title)))
:br)))
%&gt;</pre>
</td>
</tr>
</table>
<table border="1" cellpadding="2" id="simple-loop">
<caption>A simple loop</caption>
<tr>
<th>
Description
</th>
<td>
The "Music links" example with template tags and a loop. This example isn't meant to prove anything! Use the method which fits your problem!<br/>
The output of the title gets escaped by CL-EMB ("-escape html"). Depending on the situation you'd rather escape the output yourself and don't want to use any complicated modifiers in the template code itself.
</td>
</tr>
<tr>
<th>
ENV
</th>
<td>
<pre>'(:music-list
((:link "http://zappa.com/" :title "Frank Zappa")
(:link "http://marcusmiller.com/" :title "Marcus Miller")
(:link "http://www.milesdavis.com/" :title "Miles Davis")))</pre>
</td>
</tr>
<tr>
<th>
Dependencies
</th>
<td>
-
</td>
</tr>
<tr>
<td colspan="2">
<pre>&lt;h1&gt;Music links&lt;/h1&gt;
&lt;% @loop music-list %&gt;
&lt;a href=&quot;&lt;% @var link %&gt;&quot;&gt;&lt;b&gt;&lt;% @var title -escape html%&gt;&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;% @endloop %&gt;</pre>
</td>
</tr>
</table>
<table border="1" cellpadding="2" id="build-dropdown">
<caption>Build a dropdown</caption>
<tr>
<th>
Description
</th>
<td>
You can mix template style with embedded Common Lisp style. This example shows how to access the plist ENV. Within the loop (<code >@loop</code>) ENV gets bound to every plist in the list.<br/>
<a href="http://weitz.de/tbnl/">TBNL</a> is used to access a submitted parameter "product" and compare it to the current value attribute of the option element.<br/>
Remember the escaping! Set <code>cl-emb:*escape-type*</code> to <code>:html</code> and all output of <code>@var</code> will be escaped correctly.
</td>
</tr>
<tr>
<th>
ENV
</th>
<td>
<pre>'(:products
((:value "foo1" :text "Super Foo")
(:value "fooxl" :text "Super Foo XL")
(:value "bar2000" :text "Ultra Bar 2000")
(:value "hl2" :text "Half-Life 2")
(:value "dn4e4" :text "Vaporware")))</pre>
</td>
</tr>
<tr>
<th>
Dependencies
</th>
<td>
<a href="http://weitz.de/tbnl/">TBNL</a>
</td>
</tr>
<tr>
<td colspan="2">
<pre>&lt;select name=&quot;product&quot;&gt;
&lt;% @loop products %&gt;
&lt;option value=&quot;&lt;% @var value %&gt;&quot;&lt;%
(when (equal (getf env :value) (tbnl:parameter &quot;product&quot;))
%&gt; selected=&quot;selected&quot;&lt;% ) %&gt;&gt;&lt;% @var text %&gt;&lt;/option&gt;
&lt;% @endloop %&gt;
&lt;/select&gt;</pre>
</td>
</tr>
</table>
<table border="1" cellpadding="2" id="mark-fields">
<caption>Mark invalid form fields</caption>
<tr>
<th>
Description
</th>
<td>
Validate a form and mark the errors in the <em>ENV</em> plist.<br />
Again: Remember the escaping!
</td>
</tr>
<tr>
<th>
ENV
</th>
<td>
<pre>'(:email "stesch@home" :email-error t)</pre>
</td>
</tr>
<tr>
<th>
Dependencies
</th>
<td>
-
</td>
</tr>
<tr>
<td colspan="2">
<pre>&lt;% @if email-error %&gt;
&lt;span class=&quot;error&quot;&gt;Please provide valid e-mail address&lt;/span&gt;&lt;br /&gt;
&lt;% @endif %&gt;
&lt;input type=&quot;text&quot; name=&quot;email&quot; value=&quot;&lt;% @var email %&gt;&quot;/&gt;</pre>
</td>
</tr>
</table>
<table border="1" cellpadding="2" id="using-generic-templates">
<caption>Using generic templates</caption>
<tr>
<th>
Description
</th>
<td>
You want to use generic templates which can be called with a defined set of parameters? Then <code>@with</code> and <code>@endwith</code> is what you are looking for. It sets the current <em>ENV</em> to the one accessed by a given name. See the example below, which calls a template for textinput fields.
</td>
</tr>
<tr>
<th>
ENV
</th>
<td>
<pre>'(:name (:name "name"
:length 40)
:e-mail (:name "email"
:value "no@no"
:error t
:length 120))</pre>
</td>
</tr>
<tr>
<th>
Dependencies
</th>
<td>
-
</td>
</tr>
<tr>
<td colspan="2">
<pre>Please enter your name:&lt;br /&gt;
&lt;% @with name %&gt;
&lt;% @include &quot;includes/textinput.tmpl&quot; %&gt;
&lt;% @endwith %&gt;
&lt;br /&gt;
Please enter your e-mail address:&lt;br /&gt;
&lt;small&gt;(Use the TLD &lt;em&gt;.invalid&lt;/em&gt;
if you don't want to receive mail&lt;/small&gt;
&lt;% @with e-mail %&gt;
&lt;% @include &quot;includes/textinput.tmpl&quot; %&gt;
&lt;% @endwith %&gt;</pre>
</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,27 @@
CL-EMB uses parts of LSP, written by John Wiseman.
See the copyright notice and license for LSP:
---8<---8<---8<---8<---8<---8<---8<---8<---8<---8<---8<---8<---8<---8<---
Copyright (c) 2001, 2002 I/NET Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,31 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; This software is Copyright (c) Stefan Scholl, 2004.
;;; Stefan Scholl grants you the rights to distribute
;;; and use this software as governed by the terms
;;; of the Lisp Lesser GNU Public License
;;; (http://opensource.franz.com/preamble.html),
;;; known as the LLGPL.
(in-package #:cl-user)
(defpackage #:cl-emb
(:nicknames #:emb)
(:use #:cl)
(:export #:execute-emb
#:register-emb
#:pprint-emb-function
#:clear-emb
#:clear-emb-all
#:clear-emb-all-files
#:clear-expand-template-tag-hash
#:*debug*
#:*emb-start-marker*
#:*emb-end-marker*
#:*escape-type*
#:*case-sensitivity*
#:*locking-function*
#:*function-package*
#:getf*
#:construct-emb-function ))

View file

@ -0,0 +1,400 @@
Version 2.1.1
2019-04-07
Version 2.0.11
2015-08-26
Fix for type checks in LispWorks 7 (Martin Simmons)
Version 2.0.10
2015-05-28
Add :author/:license/:description fields to .asd files (Hans Huebner)
Move inlined definitions before they are used. (Stas Boukarev)
Version 2.0.9
2014-11-28
Merge branch 'master' of github.com:edicl/cl-ppcre (Hans Huebner)
Merge pull request #20 from billitch/master (Hans Huebner)
Typo in CREATE-SCANNER documentation. (Thomas de Grivel)
Version 2.0.8
2014-11-28
Update support info (Hans Huebner)
Version 2.0.7
2014-01-23
Doc: Update repository location, remove the darcs mirror. (Stas Boukarev)
Version 2.0.6
2015-01-05
Fix failing tests and spurious compiler warnings
Version 2.0.5
2014-01-05
Fix spurious test failures (Edi Weitz)
Version 2.0.4
2013-04-13
Rewrite SEQ without using recursion (Stas Boukarev)
:property and :invert-property scanning bug fix (Cyrus Harmon)
Improve documentation (David Lindes)
Version 2.0.3
2009-10-28
Use LW:SIMPLE-TEXT-STRING throughout for LispWorks
Version 2.0.2
2009-09-17
Fixed typo in chartest.lisp (caught by Peter Seibel)
Appease CCL (thanks to Hans Hübner)
Version 2.0.1
2008-09-02
Fixed faulty declaration (caught by Brent Fulgham)
Version 2.0.0
2008-07-24
Added named properties (\p{foo})
Added Unicode support
Introduced test functions for character classes
Added optional test function optimization
Cleaned up test suite, removed performance cruft
Removed the various alternative system definitions (too much maintenance work)
Exported PARSE-STRING
Changed default value of *USE-BMH-MATCHERS*
General cleanup
Lots of documentation additions
Version 1.4.1
2008-07-03
Skip non-characters in CREATE-RANGES-FROM-SET
Version 1.4.0
2008-07-03
Replaced hash tables with charsets (by Nikodemus Siivola)
Get rid of duplicates in REGEX-APROPOS(-LIST)
Version 1.3.3
2008-06-25
Let the Lisp decide how it wants to enlarge its hash tables
Fixed anchors for special variables in docs
Fixed typo in docs (thanks to Jason S. Cornez)
Version 1.3.2
2007-09-13
Updated docs and ChangeLog to be really in sync with 1.3.1 changes (thanks to Sébastien Saint-Sevin)
Version 1.3.1
2007-08-24
Second return value for REGEX-REPLACE and REGEX-REPLACE-ALL (patch by Matthew Sachs)
Version 1.3.0
2007-03-24
Optional support for named registers (patch by Ondrej Svitek)
Version 1.2.19
2007-01-16
Fixed behaviour of look-behind in repeated scans (caught by RegexCoach user Hans Jud)
Version 1.2.18
2006-10-12
Changed default element type for LispWorks
Fixed documentation for REGEX-REPLACE-ALL
Version 1.2.17
2006-10-11
Fixed bug in DO-SCANS which affected anchors (caught by RegexCoach user Laurent Taupiac)
Update link for 'man perlre' (thanks to Ricardo Boccato Alves)
Version 1.2.16
2006-07-16
Added :ELEMENT-TYPE to REGEX-REPLACE(-ALL)
Version 1.2.15
2006-07-03
Added :REGEX tag to parse tree syntax (thanks to Frédéric Jolliton)
Version 1.2.14
2006-05-24
Added missing </code> tag in docs (thanks to Wojciech Kaczmarek)
Fixed IMPORT statement for LW
Version 1.2.13
2005-12-06
Fixed bug involving *REAL-START-POS* (caught by "tichy")
Version 1.2.12
2005-11-01
REGEX-APROPOS-AUX now also uses :INHERITED
Fixed typo in parser.lisp (thanks to Derek Peschel)
Fixed value of *REGEX-CHAR-CODE-LIMIT* in docs and test (thanks to Christophe Rhodes)
Version 1.2.11
2005-08-01
Added external format for SBCL in ppcre-tests.lisp (thanks to Christophe Rhodes)
Version 1.2.10
2005-07-20
Fixed bug in CHAR-SEARCHER-AUX (caught by Peter Schuller)
Don't redefine what's already there (for LispWorks)
Version 1.2.9
2005-06-27
Hide compiler macros from CCL (thanks to Karsten Poeck)
Version 1.2.8
2005-06-10
Change EQ to EQL in REGEX-LENGTH for ANSI conformance and ABCL compatibility (thanks to Peter Graves)
Version 1.2.7
2005-05-16
Added lispworks-defsystem.lisp (thanks to Wade Humeniuk)
Fixed bug in WORD-BOUNDARY-P
Version 1.2.6
2005-04-13
Added some DEFGENERICs to appease SBCL (thanks to Alan Shields)
Removed wrong FTYPE declaration for STR (thanks to Alan Shields)
Version 1.2.5
2005-03-09
Customizable optimize qualities (thanks to Damien Kick)
Version 1.2.4
2005-03-07
Changed DEBUG optimize quality from 0 to 1
Version 1.2.3
2005-02-02
Wrapped WITH-COMPILATION-UNIT around loop in load.lisp
Version 1.2.2
2005-02-02
Fixed bug in hash table optimization (introduced in 1.1.0)
Version 1.2.1
2005-01-25
There was a wrong read-time conditional in api.lisp, sorry
Version 1.2.0
2005-01-24
AllegroCL compatibility mode
Fixed broken load.lisp file (caught by Jim Prewett and Zach Beane)
Version 1.1.0
2005-01-23
Cleaned up load.lisp and cl-ppcre.asd
Make large hash tables smaller, if possible
Correct treatment of constant regular expressions in DO-SCANS
Version 1.0.0
2004-12-22
Special anniversary release... :)
Version 0.9.4
2004-12-18
Fixed bug in NORMALIZE-VAR-LIST (caught by Dave Roberts)
Version 0.9.3
2004-12-09
Fixed bug in CREATE-SCANNER-AUX (caught by Allan Ruttenberg and Gary Byers)
Version 0.9.2
2004-12-06
More compiler macros (thanks to Allan Ruttenberg)
Version 0.9.1
2004-11-29
Shortcuts for REGISTER-GROUPS-BIND and DO-REGISTER-GROUPS (suggested by Alexander Kjeldaas)
Version 0.9.0
2004-10-14
Experimental support for "filters"
Bugfix for standalone regular expressions (ACCUMULATE-START-P wasn't set to NIL)
Version 0.8.1
2004-09-30
Patches for Genera 8.5 (thanks to Patrick O'Donnell)
Version 0.8.0
2004-09-16
Added parse tree synonyms (thanks to Patrick O'Donnell)
Version 0.7.9
2004-07-13
Fixed bug in DO-SCANS (caught by Jan Rychter)
Version 0.7.8
2004-07-13
New SIMPLE-CALLS keyword argument for REGEX-REPLACE(-ALL)
Added environment parameter to compiler macros (thanks to c.l.l article <aczhx5hj.fsf@ccs.neu.edu> by Joe Marshall)
Added compiler macros for SCAN-TO-STRINGS and REGEX-REPLACE(-ALL) (they somehow got lost)
Version 0.7.7
2004-05-19
Fixed bug in NEWLINE-SKIPPER (caught by RegexCoach user Thomas-Paz Hartman)
Added doc strings for PPCRE-SYNTAX-ERROR and friends (after playing with slime-apropos-package)
Added hyperdoc support
Version 0.7.6
2004-04-20
The closures created by CREATE-BMH-MATCHER now cleanly cope with negative arguments (bug caught by Damien Kick)
Version 0.7.5
2004-04-19
Fixed a bug with constant-length repetitions of . (dot) in single-line mode (caught by RegexCoach user Lee Gold)
Version 0.7.4
2004-02-16
Fixed wrong call to SIGNAL-PPCRE-SIGNAL-ERROR in lexer.lisp (caught by Peter Graves)
Added :CL-PPCRE to *FEATURES* (for CL-INTERPOL)
Compiler macro for SPLIT
Version 0.7.3
2004-01-28
Fixed bug in CURRENT-MIN-REST for lookaheads (reported by RegexCoach user Thomas-Paz Hartman)
Added tests for this bug
Version 0.7.2
2004-01-27
Fixed typo (SUBSEQ/NSUBSEQ) in SPLIT (thanks to Alan Ruttenberg)
Updated docs with respect to ECL (thanks to Alex Mizrahi)
Mention FreeBSD port in docs
Version 0.7.1
2003-10-24
Fixed version numbers in docs (thanks to Sébastien Saint-Sevin)
Version 0.7.0
2003-10-23
New macros REGISTER-GROUPS-BIND and DO-REGISTER-GROUPS
Added SHAREP keyword argument to most API functions and macros
Mention CL-INTERPOL in docs
Partial code cleanup (using WITH-UNIQUE-NAMES and REBINDING)
Version 0.6.1
2003-10-11
Added EXTERNAL-FORMAT keyword args to CL-PPCRE-TEST:TEST for some CLs (thanks to JP Massar and Scott D. Kalter)
Fixed bug with REGEX-REPLACE and REGEX-REPLACE-ALL when (= START END) was true
Added doc sections for quoting problems and backslash confusions (thanks to conversations with Peter Seibel)
Disable quoting in definition of QUOTE-SECTIONS so you can always safely rebuild CL-PPCRE
Version 0.6.0
2003-10-07
CL-PPCRE now has its own condition types
Added support for Perl's \Q and \E (Peter Seibel convinced me to do it) - see QUOTE-META-CHARS and *ALLOW-QUOTING*
Added tests for this new feature
Threaded tests are more verbose now and use only keyword args
Version 0.5.9
2003-10-03
Changed "^" optimizations with respect to constant end strings with offsets (bug caught by Yexuan Gui)
Added tests for this bug
Removed *.dos files from CL-PPCRE-TEST tests (thanks to JP Massar)
Added threaded tests for SBCL (thanks to Christophe Rhodes)
Version 0.5.8
2003-09-17
Optimizations for ".*" were too optimistic when look-behinds were involved
Added tests for this bug
Removed *.dos files
Version 0.5.7
2003-08-20
Fixed (CL-PPCRE:SCAN "(.)X$" "ABCX" :START 4) bug (spotted by Tibor Simko)
Forgot to export *REGEX-CHAR-CODE-LIMIT* in Corman version of DEFPACKAGE
Removed Emacs local variables from source code (finally...)
Mention Gentoo in docs
Version 0.5.6
2003-06-30
Replaced wrong COPY-REGEX code for WORD-BOUNDARY objects (detected by Max Goldberg)
Added info about possible TRUENAME problems with ACL in README (thanks to Kevin Layer for providing a patch for this)
Version 0.5.5
2003-06-09
Patch for SBCL/Debian compatibility by Kevin Rosenberg
Simpler version of compiler macro
Availability through asdf-install
Version 0.5.4
2003-04-09
Added DESTRUCTIVE keyword to CREATE-SCANNER
Version 0.5.3
2003-03-31
Fixed bug in REGEX-REPLACE (replacement string couldn't contain literal backslash)
Fixed bug in definition of CHAR-CLASS (since 0.5.0 the hash slot may be NIL - CMUCL's new PCL detects this)
Micro-optimization in INSERT-CHAR-CLASS-TESTER: CHAR-NOT-GREATERP instead of CHAR-DOWNCASE
Version 0.5.2
2003-03-28
Better compiler macro (thanks to Kent M. Pitman)
Version 0.5.1
2003-03-27
Removed compiler macro
Version 0.5.0
2003-03-27
Lexer, parser, and converter mostly re-written to reduce consing and increase speed
Get rid of FIX-POS in lexer and parser, "ism" flags are handled after parsing now
Smaller test suite (again) due to literal embedding of line breaks
Seperate test files for DOS line endings
Replaced constant +REGEX-CHAR-CODE-LIMIT+ with special variable *REGEX-CHAR-CODE-LIMIT*
Version 0.4.1
2003-03-19
Added compiler macro for SCAN
Changed test suite to be nicer to Corman Lisp and ECL (see docs for new syntax)
Incorporated visual feedback (dots) in test suite (thanks to JP Massar)
Added README file
Replaced STRING-LIST-TO-SIMPLE-STRING with a much improved version by JP Massar
Version 0.4.0
2003-02-27
Added *USE-BMH-MATCHER*
Version 0.3.2
2003-02-21
Added load.lisp
Various minor changes for Corman Lisp compatibility (thanks to Karsten Poeck and JP Massar)
Version 0.3.1
2003-01-18
Bugfix in CREATE-SCANNER (didn't work if flags were given and arg was a parse-tree)
Version 0.3.0
2003-01-12
Added new features to REGEX-REPLACE and REGEX-REPLACE-ALL
Version 0.2.0
2003-01-11
Make SPLIT more Perl-compatible, including new keyword parameters
Version 0.1.4
2003-01-10
Don't move "^" and "\A" while iterating with DO-SCANS
Added link to Debian package
Version 0.1.3
2002-12-25
More usable MK:DEFSYSTEM files (courtesy of Hannu Koivisto)
Fixed typo in documentation
Version 0.1.2
2002-12-22
Added version numbers for Debian packaging
Be friendly to case-sensitive ACL images (courtesy of Kevin Rosenberg and Douglas Crosher)
"Fixed" two cases where declarations came after docstrings (because of bugs in Corman Lisp and older CMUCL versions)
Added #-cormanlisp to hide (INCF (THE FIXNUM POS)) from Corman Lisp
Added file doc/benchmarks.2002-12-22.txt
Version 0.1.1
2002-12-21
Added asdf system definitions by Marco Baringer
Small additions to documentation
Correct (Emacs) local variables list in closures.lisp and api.lisp
Added this CHANGELOG
Version 0.1.0
2002-12-20
Initial release

View file

@ -0,0 +1,30 @@
# CL-PPCRE - Portable Perl-compatible regular expressions for Common Lisp
## Abstract
CL-PPCRE is a portable regular expression library for Common Lisp
which has the following features:
* It is **compatible with Perl** (especially when used in conjunction
with [cl-interpol](http://weitz.de/cl-interpol/), to allow
compatible parsing of regexp strings).
* It is pretty **fast**.
* It is **portable** between ANSI-compliant Common Lisp
implementations.
* It is **thread-safe**.
* In addition to specifying regular expressions as strings like in
Perl you can also use **S-expressions**.
* It comes with a
**[BSD-style license](http://www.opensource.org/licenses/bsd-license.php)**
so you can basically do with it whatever you want.
CL-PPCRE has been used successfully in various applications like
[BioBike](http://nostoc.stanford.edu/Docs/),
[clutu](http://clutu.com/),
[LoGS](http://www.hpc.unm.edu/~download/LoGS/),
[CafeSpot](http://cafespot.net/),
[Eboy](http://www.eboy.com/), or
[The Regex Coach](http://weitz.de/regex-coach/).
Further documentation can be found in `docs/index.html`, or on
[the cl-ppcre homepage](https://edicl.github.io/cl-ppcre/).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,152 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/charmap.lisp,v 1.19 2009/09/17 19:17:30 edi Exp $
;;; An optimized representation of sets of characters.
;;; Copyright (c) 2008-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defstruct (charmap (:constructor make-charmap%))
;; a bit vector mapping char codes to "booleans" (1 for set members,
;; 0 for others)
(vector #*0 :type simple-bit-vector)
;; the smallest character code of all characters in the set
(start 0 :type fixnum)
;; the upper (exclusive) bound of all character codes in the set
(end 0 :type fixnum)
;; the number of characters in the set, or NIL if this is unknown
(count nil :type (or fixnum null))
;; whether the charmap actually represents the complement of the set
(complementp nil :type boolean))
;; seems to be necessary for some Lisps like ClozureCL
(defmethod make-load-form ((map charmap) &optional environment)
(make-load-form-saving-slots map :environment environment))
(declaim (inline in-charmap-p))
(defun in-charmap-p (char charmap)
"Tests whether the character CHAR belongs to the set represented by CHARMAP."
(declare #.*standard-optimize-settings*)
(declare (character char) (charmap charmap))
(let* ((char-code (char-code char))
(char-in-vector-p
(let ((charmap-start (charmap-start charmap)))
(declare (fixnum charmap-start))
(and (<= charmap-start char-code)
(< char-code (the fixnum (charmap-end charmap)))
(= 1 (sbit (the simple-bit-vector (charmap-vector charmap))
(- char-code charmap-start)))))))
(cond ((charmap-complementp charmap) (not char-in-vector-p))
(t char-in-vector-p))))
(defun charmap-contents (charmap)
"Returns a list of all characters belonging to a character map.
Only works for non-complement charmaps."
(declare #.*standard-optimize-settings*)
(declare (charmap charmap))
(and (not (charmap-complementp charmap))
(loop for code of-type fixnum from (charmap-start charmap) to (charmap-end charmap)
for i across (the simple-bit-vector (charmap-vector charmap))
when (= i 1)
collect (code-char code))))
(defun make-charmap (start end test-function &optional complementp)
"Creates and returns a charmap representing all characters with
character codes in the interval [start end) that satisfy
TEST-FUNCTION. The COMPLEMENTP slot of the charmap is set to the
value of the optional argument, but this argument doesn't have an
effect on how TEST-FUNCTION is used."
(declare #.*standard-optimize-settings*)
(declare (fixnum start end))
(let ((vector (make-array (- end start) :element-type 'bit))
(count 0))
(declare (fixnum count))
(loop for code from start below end
for char = (code-char code)
for index from 0
when char do
(incf count)
(setf (sbit vector index) (if (funcall test-function char) 1 0)))
(make-charmap% :vector vector
:start start
:end end
;; we don't know for sure if COMPLEMENTP is true as
;; there isn't a necessary a character for each
;; integer below *REGEX-CHAR-CODE-LIMIT*
:count (and (not complementp) count)
;; make sure it's boolean
:complementp (not (not complementp)))))
(defun create-charmap-from-test-function (test-function start end)
"Creates and returns a charmap representing all characters with
character codes between START and END which satisfy TEST-FUNCTION.
Tries to find the smallest interval which is necessary to represent
the character set and uses the complement representation if that
helps."
(declare #.*standard-optimize-settings*)
(let (start-in end-in start-out end-out)
;; determine the smallest intervals containing the set and its
;; complement, [start-in, end-in) and [start-out, end-out) - first
;; the lower bound
(loop for code from start below end
for char = (code-char code)
until (and start-in start-out)
when (and char
(not start-in)
(funcall test-function char))
do (setq start-in code)
when (and char
(not start-out)
(not (funcall test-function char)))
do (setq start-out code))
(unless start-in
;; no character satisfied the test, so return a "pseudo" charmap
;; where IN-CHARMAP-P is always false
(return-from create-charmap-from-test-function
(make-charmap% :count 0)))
(unless start-out
;; no character failed the test, so return a "pseudo" charmap
;; where IN-CHARMAP-P is always true
(return-from create-charmap-from-test-function
(make-charmap% :complementp t)))
;; now determine upper bound
(loop for code from (1- end) downto start
for char = (code-char code)
until (and end-in end-out)
when (and char
(not end-in)
(funcall test-function char))
do (setq end-in (1+ code))
when (and char
(not end-out)
(not (funcall test-function char)))
do (setq end-out (1+ code)))
;; use the smaller interval
(cond ((<= (- end-in start-in) (- end-out start-out))
(make-charmap start-in end-in test-function))
(t (make-charmap start-out end-out (complement* test-function) t)))))

View file

@ -0,0 +1,242 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/charset.lisp,v 1.10 2009/09/17 19:17:30 edi Exp $
;;; A specialized set implementation for characters by Nikodemus Siivola.
;;; Copyright (c) 2008, Nikodemus Siivola. All rights reserved.
;;; Copyright (c) 2008-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defconstant +probe-depth+ 3
"Maximum number of collisions \(for any element) we accept before we
allocate more storage. This is now fixed, but could be made to vary
depending on the size of the storage vector \(e.g. in the range of
1-4). Larger probe-depths mean more collisions are tolerated before
the table grows, but increase the constant factor.")
(defun make-char-vector (size)
"Returns a vector of size SIZE to hold characters. All elements are
initialized to #\Null except for the first one which is initialized to
#\?."
(declare #.*standard-optimize-settings*)
(declare (type (integer 2 #.(1- array-total-size-limit)) size))
;; since #\Null always hashes to 0, store something else there
;; initially, and #\Null everywhere else
(let ((result (make-array size
:element-type #-:lispworks 'character #+:lispworks 'lw:simple-char
:initial-element (code-char 0))))
(setf (char result 0) #\?)
result))
(defstruct (charset (:constructor make-charset ()))
;; this is set to 0 when we stop hashing and just use a CHAR-CODE
;; indexed vector
(depth +probe-depth+ :type fixnum)
;; the number of characters in this set
(count 0 :type fixnum)
;; the storage vector
(vector (make-char-vector 12) :type (simple-array character (*))))
;; seems to be necessary for some Lisps like ClozureCL
(defmethod make-load-form ((set charset) &optional environment)
(make-load-form-saving-slots set :environment environment))
(declaim (inline mix))
(defun mix (code hash)
"Given a character code CODE and a hash code HASH, computes and
returns the \"next\" hash code. See comments below."
(declare #.*standard-optimize-settings*)
;; mixing the CHAR-CODE back in at each step makes sure that if two
;; characters collide (their hashes end up pointing in the same
;; storage vector index) on one round, they should (hopefully!) not
;; collide on the next
(sxhash (logand most-positive-fixnum (+ code hash))))
(declaim (inline compute-index))
(defun compute-index (hash vector)
"Computes and returns the index into the vector VECTOR corresponding
to the hash code HASH."
(declare #.*standard-optimize-settings*)
(1+ (mod hash (1- (length vector)))))
(defun in-charset-p (char set)
"Checks whether the character CHAR is in the charset SET."
(declare #.*standard-optimize-settings*)
(declare (character char) (charset set))
(let ((vector (charset-vector set))
(depth (charset-depth set))
(code (char-code char)))
(declare (fixnum depth))
;; as long as the set remains reasonably small, we use non-linear
;; hashing - the first hash of any character is its CHAR-CODE, and
;; subsequent hashes are computed by MIX above
(cond ((or
;; depth 0 is special - each char maps only to its code,
;; nothing else
(zerop depth)
;; index 0 is special - only #\Null maps to it, no matter
;; what the depth is
(zerop code))
(eq char (char vector code)))
(t
;; otherwise hash starts out as the character code, but
;; maps to indexes 1-N
(let ((hash code))
(tagbody
:retry
(let* ((index (compute-index hash vector))
(x (char vector index)))
(cond ((eq x (code-char 0))
;; empty, no need to probe further
(return-from in-charset-p nil))
((eq x char)
;; got it
(return-from in-charset-p t))
((zerop (decf depth))
;; max probe depth reached, nothing found
(return-from in-charset-p nil))
(t
;; nothing yet, try next place
(setf hash (mix code hash))
(go :retry))))))))))
(defun add-to-charset (char set)
"Adds the character CHAR to the charset SET, extending SET if
necessary. Returns CHAR."
(declare #.*standard-optimize-settings*)
(or (%add-to-charset char set t)
(%add-to-charset/expand char set)
(error "Oops, this should not happen..."))
char)
(defun %add-to-charset (char set count)
"Tries to add the character CHAR to the charset SET without
extending it. Returns NIL if this fails. Counts CHAR as new
if COUNT is true and it is added to SET."
(declare #.*standard-optimize-settings*)
(declare (character char) (charset set))
(let ((vector (charset-vector set))
(depth (charset-depth set))
(code (char-code char)))
(declare (fixnum depth))
;; see comments in IN-CHARSET-P for algorithm
(cond ((or (zerop depth) (zerop code))
(unless (eq char (char vector code))
(setf (char vector code) char)
(when count
(incf (charset-count set))))
char)
(t
(let ((hash code))
(tagbody
:retry
(let* ((index (compute-index hash vector))
(x (char vector index)))
(cond ((eq x (code-char 0))
(setf (char vector index) char)
(when count
(incf (charset-count set)))
(return-from %add-to-charset char))
((eq x char)
(return-from %add-to-charset char))
((zerop (decf depth))
;; need to expand the table
(return-from %add-to-charset nil))
(t
(setf hash (mix code hash))
(go :retry))))))))))
(defun %add-to-charset/expand (char set)
"Extends the charset SET and then adds the character CHAR to it."
(declare #.*standard-optimize-settings*)
(declare (character char) (charset set))
(let* ((old-vector (charset-vector set))
(new-size (* 2 (length old-vector))))
(tagbody
:retry
;; when the table grows large (currently over 1/3 of
;; CHAR-CODE-LIMIT), we dispense with hashing and just allocate a
;; storage vector with space for all characters, so that each
;; character always uses only the CHAR-CODE
(multiple-value-bind (new-depth new-vector)
(if (>= new-size #.(truncate char-code-limit 3))
(values 0 (make-char-vector char-code-limit))
(values +probe-depth+ (make-char-vector new-size)))
(setf (charset-depth set) new-depth
(charset-vector set) new-vector)
(flet ((try-add (x)
;; don't count - old characters are already accounted
;; for, and might count the new one multiple times as
;; well
(unless (%add-to-charset x set nil)
(assert (not (zerop new-depth)))
(setf new-size (* 2 new-size))
(go :retry))))
(try-add char)
(dotimes (i (length old-vector))
(let ((x (char old-vector i)))
(if (eq x (code-char 0))
(when (zerop i)
(try-add x))
(unless (zerop i)
(try-add x))))))))
;; added and expanded, /now/ count the new character.
(incf (charset-count set))
t))
(defun map-charset (function charset)
"Calls FUNCTION with all characters in SET. Returns NIL."
(declare #.*standard-optimize-settings*)
(declare (function function))
(let* ((n (charset-count charset))
(vector (charset-vector charset))
(size (length vector)))
;; see comments in IN-CHARSET-P for algorithm
(when (eq (code-char 0) (char vector 0))
(funcall function (code-char 0))
(decf n))
(loop for i from 1 below size
for char = (char vector i)
unless (eq (code-char 0) char) do
(funcall function char)
;; this early termination test should be worth it when
;; mapping across depth 0 charsets.
(when (zerop (decf n))
(return-from map-charset nil))))
nil)
(defun create-charset-from-test-function (test-function start end)
"Creates and returns a charset representing all characters with
character codes between START and END which satisfy TEST-FUNCTION."
(declare #.*standard-optimize-settings*)
(loop with charset = (make-charset)
for code from start below end
for char = (code-char code)
when (and char (funcall test-function char))
do (add-to-charset char charset)
finally (return charset)))

View file

@ -0,0 +1,98 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/chartest.lisp,v 1.5 2009/09/17 19:17:30 edi Exp $
;;; Copyright (c) 2008-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defun create-hash-table-from-test-function (test-function start end)
"Creates and returns a hash table representing all characters with
character codes between START and END which satisfy TEST-FUNCTION."
(declare #.*standard-optimize-settings*)
(loop with hash-table = (make-hash-table)
for code from start below end
for char = (code-char code)
when (and char (funcall test-function char))
do (setf (gethash char hash-table) t)
finally (return hash-table)))
(defun create-optimized-test-function (test-function &key
(start 0)
(end *regex-char-code-limit*)
(kind *optimize-char-classes*))
"Given a unary test function which is applicable to characters
returns a function which yields the same boolean results for all
characters with character codes from START to \(excluding) END. If
KIND is NIL, TEST-FUNCTION will simply be returned. Otherwise, KIND
should be one of:
* :HASH-TABLE - builds a hash table representing all characters which
satisfy the test and returns a closure which checks if
a character is in that hash table
* :CHARSET - instead of a hash table uses a \"charset\" which is a
data structure using non-linear hashing and optimized to
represent \(sparse) sets of characters in a fast and
space-efficient way \(contributed by Nikodemus Siivola)
* :CHARMAP - instead of a hash table uses a bit vector to represent
the set of characters
You can also use :HASH-TABLE* or :CHARSET* which are like :HASH-TABLE
and :CHARSET but use the complement of the set if the set contains
more than half of all characters between START and END. This saves
space but needs an additional pass across all characters to create the
data structure. There is no corresponding :CHARMAP* kind as the bit
vectors are already created to cover the smallest possible interval
which contains either the set or its complement."
(declare #.*standard-optimize-settings*)
(ecase kind
((nil) test-function)
(:charmap
(let ((charmap (create-charmap-from-test-function test-function start end)))
(lambda (char)
(in-charmap-p char charmap))))
((:charset :charset*)
(let ((charset (create-charset-from-test-function test-function start end)))
(cond ((or (eq kind :charset)
(<= (charset-count charset) (ceiling (- end start) 2)))
(lambda (char)
(in-charset-p char charset)))
(t (setq charset (create-charset-from-test-function (complement* test-function)
start end))
(lambda (char)
(not (in-charset-p char charset)))))))
((:hash-table :hash-table*)
(let ((hash-table (create-hash-table-from-test-function test-function start end)))
(cond ((or (eq kind :hash-table)
(<= (hash-table-count hash-table) (ceiling (- end start) 2)))
(lambda (char)
(gethash char hash-table)))
(t (setq hash-table (create-hash-table-from-test-function (complement* test-function)
start end))
(lambda (char)
(not (gethash char hash-table)))))))))

View file

@ -0,0 +1,64 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/cl-ppcre-unicode.asd,v 1.15 2009/09/17 19:17:30 edi Exp $
;;; This ASDF system definition was kindly provided by Marco Baringer.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-user)
(defpackage :cl-ppcre-unicode-asd
(:use :cl :asdf))
(in-package :cl-ppcre-unicode-asd)
(defsystem :cl-ppcre-unicode
:description "Perl-compatible regular expression library (Unicode)"
:author "Dr. Edi Weitz"
:license "BSD"
:components ((:module "cl-ppcre-unicode"
:serial t
:components ((:file "packages")
(:file "resolver"))))
:depends-on (:cl-ppcre :cl-unicode))
(defsystem :cl-ppcre-unicode-test
:description "Perl-compatible regular expression library tests (Unicode)"
:author "Dr. Edi Weitz"
:license "BSD"
:depends-on (:cl-ppcre-unicode :cl-ppcre-test)
:components ((:module "test"
:serial t
:components ((:file "unicode-tests")))))
(defmethod perform ((o test-op) (c (eql (find-system :cl-ppcre-unicode))))
;; we must load CL-PPCRE explicitly so that the CL-PPCRE-TEST system
;; will be found
(operate 'load-op :cl-ppcre)
(operate 'load-op :cl-ppcre-unicode-test)
(funcall (intern (symbol-name :run-all-tests) (find-package :cl-ppcre-test))
:more-tests (intern (symbol-name :unicode-test) (find-package :cl-ppcre-test))))

View file

@ -0,0 +1,38 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/cl-ppcre-unicode/packages.lisp,v 1.3 2009/09/17 19:17:34 edi Exp $
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-user)
(defpackage :cl-ppcre-unicode
#+:genera
(:shadowing-import-from :common-lisp :lambda :string)
(:use #-:genera :cl #+:genera :future-common-lisp
:cl-ppcre :cl-unicode)
(:import-from :cl-ppcre :signal-syntax-error)
(:export :unicode-property-resolver))

View file

@ -0,0 +1,61 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/cl-ppcre-unicode/resolver.lisp,v 1.5 2008/07/23 02:14:08 edi Exp $
;;; Copyright (c) 2008, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre-unicode)
(defun unicode-property-resolver (property-name)
"A property resolver which understands Unicode properties using
CL-UNICODE's PROPERTY-TEST function. This resolver is automatically
installed in *PROPERTY-RESOLVER* when the CL-PPCRE-UNICODE system is
loaded."
(or (property-test property-name :errorp nil)
(signal-syntax-error "There is no property named ~S." property-name)))
(setq *property-resolver* 'unicode-property-resolver)
(pushnew :cl-ppcre-unicode *features*)
;; stuff for Nikodemus Siivola's HYPERDOC
;; see <http://common-lisp.net/project/hyperdoc/>
;; and <http://www.cliki.net/hyperdoc>
;; also used by LW-ADD-ONS
(defvar *hyperdoc-base-uri* "http://weitz.de/cl-ppcre/")
(let ((exported-symbols-alist
(loop for symbol being the external-symbols of :cl-ppcre-unicode
collect (cons symbol
(concatenate 'string
"#"
(string-downcase symbol))))))
(defun hyperdoc-lookup (symbol type)
(declare (ignore type))
(cdr (assoc symbol
exported-symbols-alist
:test #'eq))))

View file

@ -0,0 +1,85 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/cl-ppcre.asd,v 1.49 2009/10/28 07:36:15 edi Exp $
;;; This ASDF system definition was kindly provided by Marco Baringer.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-user)
(defpackage :cl-ppcre-asd
(:use :cl :asdf))
(in-package :cl-ppcre-asd)
(defsystem :cl-ppcre
:version "2.1.1"
:description "Perl-compatible regular expression library"
:author "Dr. Edi Weitz"
:license "BSD"
:serial t
:components ((:file "packages")
(:file "specials")
(:file "util")
(:file "errors")
(:file "charset")
(:file "charmap")
(:file "chartest")
#-:use-acl-regexp2-engine
(:file "lexer")
#-:use-acl-regexp2-engine
(:file "parser")
#-:use-acl-regexp2-engine
(:file "regex-class")
#-:use-acl-regexp2-engine
(:file "regex-class-util")
#-:use-acl-regexp2-engine
(:file "convert")
#-:use-acl-regexp2-engine
(:file "optimize")
#-:use-acl-regexp2-engine
(:file "closures")
#-:use-acl-regexp2-engine
(:file "repetition-closures")
#-:use-acl-regexp2-engine
(:file "scanner")
(:file "api")))
(defsystem :cl-ppcre-test
:description "Perl-compatible regular expression library tests"
:author "Dr. Edi Weitz"
:license "BSD"
:depends-on (:cl-ppcre :flexi-streams)
:components ((:module "test"
:serial t
:components ((:file "packages")
(:file "tests")
(:file "perl-tests")))))
(defmethod perform ((o test-op) (c (eql (find-system :cl-ppcre))))
(operate 'load-op :cl-ppcre-test)
(funcall (intern (symbol-name :run-all-tests) (find-package :cl-ppcre-test))))

View file

@ -0,0 +1,471 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/closures.lisp,v 1.45 2009/09/17 19:17:30 edi Exp $
;;; Here we create the closures which together build the final
;;; scanner.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(declaim (inline *string*= *string*-equal))
(defun *string*= (string2 start1 end1 start2 end2)
"Like STRING=, i.e. compares the special string *STRING* from START1
to END1 with STRING2 from START2 to END2. Note that there's no
boundary check - this has to be implemented by the caller."
(declare #.*standard-optimize-settings*)
(declare (fixnum start1 end1 start2 end2))
(loop for string1-idx of-type fixnum from start1 below end1
for string2-idx of-type fixnum from start2 below end2
always (char= (schar *string* string1-idx)
(schar string2 string2-idx))))
(defun *string*-equal (string2 start1 end1 start2 end2)
"Like STRING-EQUAL, i.e. compares the special string *STRING* from
START1 to END1 with STRING2 from START2 to END2. Note that there's no
boundary check - this has to be implemented by the caller."
(declare #.*standard-optimize-settings*)
(declare (fixnum start1 end1 start2 end2))
(loop for string1-idx of-type fixnum from start1 below end1
for string2-idx of-type fixnum from start2 below end2
always (char-equal (schar *string* string1-idx)
(schar string2 string2-idx))))
(defgeneric create-matcher-aux (regex next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which takes one parameter,
START-POS, and tests whether REGEX can match *STRING* at START-POS
such that the call to NEXT-FN after the match would succeed."))
(defmethod create-matcher-aux ((seq seq) next-fn)
(declare #.*standard-optimize-settings*)
;; the closure for a SEQ is a chain of closures for the elements of
;; this sequence which call each other in turn; the last closure
;; calls NEXT-FN
(loop for element in (reverse (elements seq))
for curr-matcher = next-fn then next-matcher
for next-matcher = (create-matcher-aux element curr-matcher)
finally (return next-matcher)))
(defmethod create-matcher-aux ((alternation alternation) next-fn)
(declare #.*standard-optimize-settings*)
;; first create closures for all alternations of ALTERNATION
(let ((all-matchers (mapcar #'(lambda (choice)
(create-matcher-aux choice next-fn))
(choices alternation))))
;; now create a closure which checks if one of the closures
;; created above can succeed
(lambda (start-pos)
(declare (fixnum start-pos))
(loop for matcher in all-matchers
thereis (funcall (the function matcher) start-pos)))))
(defmethod create-matcher-aux ((register register) next-fn)
(declare #.*standard-optimize-settings*)
;; the position of this REGISTER within the whole regex; we start to
;; count at 0
(let ((num (num register)))
(declare (fixnum num))
;; STORE-END-OF-REG is a thin wrapper around NEXT-FN which will
;; update the corresponding values of *REGS-START* and *REGS-END*
;; after the inner matcher has succeeded
(flet ((store-end-of-reg (start-pos)
(declare (fixnum start-pos)
(function next-fn))
(setf (svref *reg-starts* num) (svref *regs-maybe-start* num)
(svref *reg-ends* num) start-pos)
(funcall next-fn start-pos)))
;; the inner matcher is a closure corresponding to the regex
;; wrapped by this REGISTER
(let ((inner-matcher (create-matcher-aux (regex register)
#'store-end-of-reg)))
(declare (function inner-matcher))
;; here comes the actual closure for REGISTER
(lambda (start-pos)
(declare (fixnum start-pos))
;; remember the old values of *REGS-START* and friends in
;; case we cannot match
(let ((old-*reg-starts* (svref *reg-starts* num))
(old-*regs-maybe-start* (svref *regs-maybe-start* num))
(old-*reg-ends* (svref *reg-ends* num)))
;; we cannot use *REGS-START* here because Perl allows
;; regular expressions like /(a|\1x)*/
(setf (svref *regs-maybe-start* num) start-pos)
(let ((next-pos (funcall inner-matcher start-pos)))
(unless next-pos
;; restore old values on failure
(setf (svref *reg-starts* num) old-*reg-starts*
(svref *regs-maybe-start* num) old-*regs-maybe-start*
(svref *reg-ends* num) old-*reg-ends*))
next-pos)))))))
(defmethod create-matcher-aux ((lookahead lookahead) next-fn)
(declare #.*standard-optimize-settings*)
;; create a closure which just checks for the inner regex and
;; doesn't care about NEXT-FN
(let ((test-matcher (create-matcher-aux (regex lookahead) #'identity)))
(declare (function next-fn test-matcher))
(if (positivep lookahead)
;; positive look-ahead: check success of inner regex, then call
;; NEXT-FN
(lambda (start-pos)
(and (funcall test-matcher start-pos)
(funcall next-fn start-pos)))
;; negative look-ahead: check failure of inner regex, then call
;; NEXT-FN
(lambda (start-pos)
(and (not (funcall test-matcher start-pos))
(funcall next-fn start-pos))))))
(defmethod create-matcher-aux ((lookbehind lookbehind) next-fn)
(declare #.*standard-optimize-settings*)
(let ((len (len lookbehind))
;; create a closure which just checks for the inner regex and
;; doesn't care about NEXT-FN
(test-matcher (create-matcher-aux (regex lookbehind) #'identity)))
(declare (function next-fn test-matcher)
(fixnum len))
(if (positivep lookbehind)
;; positive look-behind: check success of inner regex (if we're
;; far enough from the start of *STRING*), then call NEXT-FN
(lambda (start-pos)
(declare (fixnum start-pos))
(and (>= (- start-pos (or *real-start-pos* *start-pos*)) len)
(funcall test-matcher (- start-pos len))
(funcall next-fn start-pos)))
;; negative look-behind: check failure of inner regex (if we're
;; far enough from the start of *STRING*), then call NEXT-FN
(lambda (start-pos)
(declare (fixnum start-pos))
(and (or (< (- start-pos (or *real-start-pos* *start-pos*)) len)
(not (funcall test-matcher (- start-pos len))))
(funcall next-fn start-pos))))))
(defmacro insert-char-class-tester ((char-class chr-expr) &body body)
"Utility macro to replace each occurence of '\(CHAR-CLASS-TEST)
within BODY with the correct test (corresponding to CHAR-CLASS)
against CHR-EXPR."
(with-rebinding (char-class)
(with-unique-names (test-function)
(flet ((substitute-char-class-tester (new)
(subst new '(char-class-test) body
:test #'equalp)))
`(let ((,test-function (test-function ,char-class)))
,@(substitute-char-class-tester
`(funcall ,test-function ,chr-expr)))))))
(defmethod create-matcher-aux ((char-class char-class) next-fn)
(declare #.*standard-optimize-settings*)
(declare (function next-fn))
;; insert a test against the current character within *STRING*
(insert-char-class-tester (char-class (schar *string* start-pos))
(lambda (start-pos)
(declare (fixnum start-pos))
(and (< start-pos *end-pos*)
(char-class-test)
(funcall next-fn (1+ start-pos))))))
(defmethod create-matcher-aux ((str str) next-fn)
(declare #.*standard-optimize-settings*)
(declare (fixnum *end-string-pos*)
(function next-fn)
;; this special value is set by CREATE-SCANNER when the
;; closures are built
(special end-string))
(let* ((len (len str))
(case-insensitive-p (case-insensitive-p str))
(start-of-end-string-p (start-of-end-string-p str))
(skip (skip str))
(str (str str))
(chr (schar str 0))
(end-string (and end-string (str end-string)))
(end-string-len (if end-string
(length end-string)
nil)))
(declare (fixnum len))
(cond ((and start-of-end-string-p case-insensitive-p)
;; closure for the first STR which belongs to the constant
;; string at the end of the regular expression;
;; case-insensitive version
(lambda (start-pos)
(declare (fixnum start-pos end-string-len))
(let ((test-end-pos (+ start-pos end-string-len)))
(declare (fixnum test-end-pos))
;; either we're at *END-STRING-POS* (which means that
;; it has already been confirmed that end-string
;; starts here) or we really have to test
(and (or (= start-pos *end-string-pos*)
(and (<= test-end-pos *end-pos*)
(*string*-equal end-string start-pos test-end-pos
0 end-string-len)))
(funcall next-fn (+ start-pos len))))))
(start-of-end-string-p
;; closure for the first STR which belongs to the constant
;; string at the end of the regular expression;
;; case-sensitive version
(lambda (start-pos)
(declare (fixnum start-pos end-string-len))
(let ((test-end-pos (+ start-pos end-string-len)))
(declare (fixnum test-end-pos))
;; either we're at *END-STRING-POS* (which means that
;; it has already been confirmed that end-string
;; starts here) or we really have to test
(and (or (= start-pos *end-string-pos*)
(and (<= test-end-pos *end-pos*)
(*string*= end-string start-pos test-end-pos
0 end-string-len)))
(funcall next-fn (+ start-pos len))))))
(skip
;; a STR which can be skipped because some other function
;; has already confirmed that it matches
(lambda (start-pos)
(declare (fixnum start-pos))
(funcall next-fn (+ start-pos len))))
((and (= len 1) case-insensitive-p)
;; STR represent exactly one character; case-insensitive
;; version
(lambda (start-pos)
(declare (fixnum start-pos))
(and (< start-pos *end-pos*)
(char-equal (schar *string* start-pos) chr)
(funcall next-fn (1+ start-pos)))))
((= len 1)
;; STR represent exactly one character; case-sensitive
;; version
(lambda (start-pos)
(declare (fixnum start-pos))
(and (< start-pos *end-pos*)
(char= (schar *string* start-pos) chr)
(funcall next-fn (1+ start-pos)))))
(case-insensitive-p
;; general case, case-insensitive version
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((next-pos (+ start-pos len)))
(declare (fixnum next-pos))
(and (<= next-pos *end-pos*)
(*string*-equal str start-pos next-pos 0 len)
(funcall next-fn next-pos)))))
(t
;; general case, case-sensitive version
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((next-pos (+ start-pos len)))
(declare (fixnum next-pos))
(and (<= next-pos *end-pos*)
(*string*= str start-pos next-pos 0 len)
(funcall next-fn next-pos))))))))
(declaim (inline word-boundary-p))
(defun word-boundary-p (start-pos)
"Check whether START-POS is a word-boundary within *STRING*."
(declare #.*standard-optimize-settings*)
(declare (fixnum start-pos))
(let ((1-start-pos (1- start-pos))
(*start-pos* (or *real-start-pos* *start-pos*)))
;; either the character before START-POS is a word-constituent and
;; the character at START-POS isn't...
(or (and (or (= start-pos *end-pos*)
(and (< start-pos *end-pos*)
(not (word-char-p (schar *string* start-pos)))))
(and (< 1-start-pos *end-pos*)
(<= *start-pos* 1-start-pos)
(word-char-p (schar *string* 1-start-pos))))
;; ...or vice versa
(and (or (= start-pos *start-pos*)
(and (< 1-start-pos *end-pos*)
(<= *start-pos* 1-start-pos)
(not (word-char-p (schar *string* 1-start-pos)))))
(and (< start-pos *end-pos*)
(word-char-p (schar *string* start-pos)))))))
(defmethod create-matcher-aux ((word-boundary word-boundary) next-fn)
(declare #.*standard-optimize-settings*)
(declare (function next-fn))
(if (negatedp word-boundary)
(lambda (start-pos)
(and (not (word-boundary-p start-pos))
(funcall next-fn start-pos)))
(lambda (start-pos)
(and (word-boundary-p start-pos)
(funcall next-fn start-pos)))))
(defmethod create-matcher-aux ((everything everything) next-fn)
(declare #.*standard-optimize-settings*)
(declare (function next-fn))
(if (single-line-p everything)
;; closure for single-line-mode: we really match everything, so we
;; just advance the index into *STRING* by one and carry on
(lambda (start-pos)
(declare (fixnum start-pos))
(and (< start-pos *end-pos*)
(funcall next-fn (1+ start-pos))))
;; not single-line-mode, so we have to make sure we don't match
;; #\Newline
(lambda (start-pos)
(declare (fixnum start-pos))
(and (< start-pos *end-pos*)
(char/= (schar *string* start-pos) #\Newline)
(funcall next-fn (1+ start-pos))))))
(defmethod create-matcher-aux ((anchor anchor) next-fn)
(declare #.*standard-optimize-settings*)
(declare (function next-fn))
(let ((startp (startp anchor))
(multi-line-p (multi-line-p anchor)))
(cond ((no-newline-p anchor)
;; this must be an end-anchor and it must be modeless, so
;; we just have to check whether START-POS equals
;; *END-POS*
(lambda (start-pos)
(declare (fixnum start-pos))
(and (= start-pos *end-pos*)
(funcall next-fn start-pos))))
((and startp multi-line-p)
;; a start-anchor in multi-line-mode: check if we're at
;; *START-POS* or if the last character was #\Newline
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((*start-pos* (or *real-start-pos* *start-pos*)))
(and (or (= start-pos *start-pos*)
(and (<= start-pos *end-pos*)
(> start-pos *start-pos*)
(char= #\Newline
(schar *string* (1- start-pos)))))
(funcall next-fn start-pos)))))
(startp
;; a start-anchor which is not in multi-line-mode, so just
;; check whether we're at *START-POS*
(lambda (start-pos)
(declare (fixnum start-pos))
(and (= start-pos (or *real-start-pos* *start-pos*))
(funcall next-fn start-pos))))
(multi-line-p
;; an end-anchor in multi-line-mode: check if we're at
;; *END-POS* or if the character we're looking at is
;; #\Newline
(lambda (start-pos)
(declare (fixnum start-pos))
(and (or (= start-pos *end-pos*)
(and (< start-pos *end-pos*)
(char= #\Newline
(schar *string* start-pos))))
(funcall next-fn start-pos))))
(t
;; an end-anchor which is not in multi-line-mode, so just
;; check if we're at *END-POS* or if we're looking at
;; #\Newline and there's nothing behind it
(lambda (start-pos)
(declare (fixnum start-pos))
(and (or (= start-pos *end-pos*)
(and (= start-pos (1- *end-pos*))
(char= #\Newline
(schar *string* start-pos))))
(funcall next-fn start-pos)))))))
(defmethod create-matcher-aux ((back-reference back-reference) next-fn)
(declare #.*standard-optimize-settings*)
(declare (function next-fn))
;; the position of the corresponding REGISTER within the whole
;; regex; we start to count at 0
(let ((num (num back-reference)))
(if (case-insensitive-p back-reference)
;; the case-insensitive version
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((reg-start (svref *reg-starts* num))
(reg-end (svref *reg-ends* num)))
;; only bother to check if the corresponding REGISTER as
;; matched successfully already
(and reg-start
(let ((next-pos (+ start-pos (- (the fixnum reg-end)
(the fixnum reg-start)))))
(declare (fixnum next-pos))
(and
(<= next-pos *end-pos*)
(*string*-equal *string* start-pos next-pos
reg-start reg-end)
(funcall next-fn next-pos))))))
;; the case-sensitive version
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((reg-start (svref *reg-starts* num))
(reg-end (svref *reg-ends* num)))
;; only bother to check if the corresponding REGISTER as
;; matched successfully already
(and reg-start
(let ((next-pos (+ start-pos (- (the fixnum reg-end)
(the fixnum reg-start)))))
(declare (fixnum next-pos))
(and
(<= next-pos *end-pos*)
(*string*= *string* start-pos next-pos
reg-start reg-end)
(funcall next-fn next-pos)))))))))
(defmethod create-matcher-aux ((branch branch) next-fn)
(declare #.*standard-optimize-settings*)
(let* ((test (test branch))
(then-matcher (create-matcher-aux (then-regex branch) next-fn))
(else-matcher (create-matcher-aux (else-regex branch) next-fn)))
(declare (function then-matcher else-matcher))
(cond ((numberp test)
(lambda (start-pos)
(declare (fixnum test))
(if (and (< test (length *reg-starts*))
(svref *reg-starts* test))
(funcall then-matcher start-pos)
(funcall else-matcher start-pos))))
(t
(let ((test-matcher (create-matcher-aux test #'identity)))
(declare (function test-matcher))
(lambda (start-pos)
(if (funcall test-matcher start-pos)
(funcall then-matcher start-pos)
(funcall else-matcher start-pos))))))))
(defmethod create-matcher-aux ((standalone standalone) next-fn)
(declare #.*standard-optimize-settings*)
(let ((inner-matcher (create-matcher-aux (regex standalone) #'identity)))
(declare (function next-fn inner-matcher))
(lambda (start-pos)
(let ((next-pos (funcall inner-matcher start-pos)))
(and next-pos
(funcall next-fn next-pos))))))
(defmethod create-matcher-aux ((filter filter) next-fn)
(declare #.*standard-optimize-settings*)
(let ((fn (fn filter)))
(lambda (start-pos)
(let ((next-pos (funcall fn start-pos)))
(and next-pos
(funcall next-fn next-pos))))))
(defmethod create-matcher-aux ((void void) next-fn)
(declare #.*standard-optimize-settings*)
;; optimize away VOIDs: don't create a closure, just return NEXT-FN
next-fn)

View file

@ -0,0 +1,879 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/convert.lisp,v 1.57 2009/09/17 19:17:31 edi Exp $
;;; Here the parse tree is converted into its internal representation
;;; using REGEX objects. At the same time some optimizations are
;;; already applied.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
;;; The flags that represent the "ism" modifiers are always kept
;;; together in a three-element list. We use the following macros to
;;; access individual elements.
(defmacro case-insensitive-mode-p (flags)
"Accessor macro to extract the first flag out of a three-element flag list."
`(first ,flags))
(defmacro multi-line-mode-p (flags)
"Accessor macro to extract the second flag out of a three-element flag list."
`(second ,flags))
(defmacro single-line-mode-p (flags)
"Accessor macro to extract the third flag out of a three-element flag list."
`(third ,flags))
(defun set-flag (token)
"Reads a flag token and sets or unsets the corresponding entry in
the special FLAGS list."
(declare #.*standard-optimize-settings*)
(declare (special flags))
(case token
((:case-insensitive-p)
(setf (case-insensitive-mode-p flags) t))
((:case-sensitive-p)
(setf (case-insensitive-mode-p flags) nil))
((:multi-line-mode-p)
(setf (multi-line-mode-p flags) t))
((:not-multi-line-mode-p)
(setf (multi-line-mode-p flags) nil))
((:single-line-mode-p)
(setf (single-line-mode-p flags) t))
((:not-single-line-mode-p)
(setf (single-line-mode-p flags) nil))
(otherwise
(signal-syntax-error "Unknown flag token ~A." token))))
(defgeneric resolve-property (property)
(:documentation "Resolves PROPERTY to a unary character test
function. PROPERTY can either be a function designator or it can be a
string which is resolved using *PROPERTY-RESOLVER*.")
(:method ((property-name string))
(funcall *property-resolver* property-name))
(:method ((function-name symbol))
function-name)
(:method ((test-function function))
test-function))
(defun convert-char-class-to-test-function (list invertedp case-insensitive-p)
"Combines all items in LIST into test function and returns a
logical-OR combination of these functions. Items can be single
characters, character ranges like \(:RANGE #\\A #\\E), or special
character classes like :DIGIT-CLASS. Does the right thing with
respect to case-\(in)sensitivity as specified by the special variable
FLAGS."
(declare #.*standard-optimize-settings*)
(declare (special flags))
(let ((test-functions
(loop for item in list
collect (cond ((characterp item)
;; rebind so closure captures the right one
(let ((this-char item))
(lambda (char)
(declare (character char this-char))
(char= char this-char))))
((symbolp item)
(case item
((:digit-class) #'digit-char-p)
((:non-digit-class) (complement* #'digit-char-p))
((:whitespace-char-class) #'whitespacep)
((:non-whitespace-char-class) (complement* #'whitespacep))
((:word-char-class) #'word-char-p)
((:non-word-char-class) (complement* #'word-char-p))
(otherwise
(signal-syntax-error "Unknown symbol ~A in character class." item))))
((and (consp item)
(eq (first item) :property))
(resolve-property (second item)))
((and (consp item)
(eq (first item) :inverted-property))
(complement* (resolve-property (second item))))
((and (consp item)
(eq (first item) :range))
(let ((from (second item))
(to (third item)))
(when (char> from to)
(signal-syntax-error "Invalid range from ~S to ~S in char-class." from to))
(lambda (char)
(declare (character char from to))
(char<= from char to))))
(t (signal-syntax-error "Unknown item ~A in char-class list." item))))))
(unless test-functions
(signal-syntax-error "Empty character class."))
(cond ((cdr test-functions)
(cond ((and invertedp case-insensitive-p)
(lambda (char)
(declare (character char))
(loop with both-case-p = (both-case-p char)
with char-down = (if both-case-p (char-downcase char) char)
with char-up = (if both-case-p (char-upcase char) nil)
for test-function in test-functions
never (or (funcall test-function char-down)
(and char-up (funcall test-function char-up))))))
(case-insensitive-p
(lambda (char)
(declare (character char))
(loop with both-case-p = (both-case-p char)
with char-down = (if both-case-p (char-downcase char) char)
with char-up = (if both-case-p (char-upcase char) nil)
for test-function in test-functions
thereis (or (funcall test-function char-down)
(and char-up (funcall test-function char-up))))))
(invertedp
(lambda (char)
(loop for test-function in test-functions
never (funcall test-function char))))
(t
(lambda (char)
(loop for test-function in test-functions
thereis (funcall test-function char))))))
;; there's only one test-function
(t (let ((test-function (first test-functions)))
(cond ((and invertedp case-insensitive-p)
(lambda (char)
(declare (character char))
(not (or (funcall test-function (char-downcase char))
(and (both-case-p char)
(funcall test-function (char-upcase char)))))))
(case-insensitive-p
(lambda (char)
(declare (character char))
(or (funcall test-function (char-downcase char))
(and (both-case-p char)
(funcall test-function (char-upcase char))))))
(invertedp (complement* test-function))
(t test-function)))))))
(defun maybe-split-repetition (regex
greedyp
minimum
maximum
min-len
length
reg-seen)
"Splits a REPETITION object into a constant and a varying part if
applicable, i.e. something like
a{3,} -> a{3}a*
The arguments to this function correspond to the REPETITION slots of
the same name."
(declare #.*standard-optimize-settings*)
(declare (fixnum minimum)
(type (or fixnum null) maximum))
;; note the usage of COPY-REGEX here; we can't use the same REGEX
;; object in both REPETITIONS because they will have different
;; offsets
(when maximum
(when (zerop maximum)
;; trivial case: don't repeat at all
(return-from maybe-split-repetition
(make-instance 'void)))
(when (= 1 minimum maximum)
;; another trivial case: "repeat" exactly once
(return-from maybe-split-repetition
regex)))
;; first set up the constant part of the repetition
;; maybe that's all we need
(let ((constant-repetition (if (plusp minimum)
(make-instance 'repetition
:regex (copy-regex regex)
:greedyp greedyp
:minimum minimum
:maximum minimum
:min-len min-len
:len length
:contains-register-p reg-seen)
;; don't create garbage if minimum is 0
nil)))
(when (and maximum
(= maximum minimum))
(return-from maybe-split-repetition
;; no varying part needed because min = max
constant-repetition))
;; now construct the varying part
(let ((varying-repetition
(make-instance 'repetition
:regex regex
:greedyp greedyp
:minimum 0
:maximum (if maximum (- maximum minimum) nil)
:min-len min-len
:len length
:contains-register-p reg-seen)))
(cond ((zerop minimum)
;; min = 0, no constant part needed
varying-repetition)
((= 1 minimum)
;; min = 1, constant part needs no REPETITION wrapped around
(make-instance 'seq
:elements (list (copy-regex regex)
varying-repetition)))
(t
;; general case
(make-instance 'seq
:elements (list constant-repetition
varying-repetition)))))))
;; During the conversion of the parse tree we keep track of the start
;; of the parse tree in the special variable STARTS-WITH which'll
;; either hold a STR object or an EVERYTHING object. The latter is the
;; case if the regex starts with ".*" which implicitly anchors the
;; regex at the start (perhaps modulo #\Newline).
(defun maybe-accumulate (str)
"Accumulate STR into the special variable STARTS-WITH if
ACCUMULATE-START-P (also special) is true and STARTS-WITH is either
NIL or a STR object of the same case mode. Always returns NIL."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p starts-with))
(declare (ftype (function (t) fixnum) len))
(when accumulate-start-p
(etypecase starts-with
(str
;; STARTS-WITH already holds a STR, so we check if we can
;; concatenate
(cond ((eq (case-insensitive-p starts-with)
(case-insensitive-p str))
;; we modify STARTS-WITH in place
(setf (len starts-with)
(+ (len starts-with) (len str)))
;; note that we use SLOT-VALUE because the accessor
;; STR has a declared FTYPE which doesn't fit here
(adjust-array (slot-value starts-with 'str)
(len starts-with)
:fill-pointer t)
(setf (subseq (slot-value starts-with 'str)
(- (len starts-with) (len str)))
(str str)
;; STR objects that are parts of STARTS-WITH
;; always have their SKIP slot set to true
;; because the SCAN function will take care of
;; them, i.e. the matcher can ignore them
(skip str) t))
(t (setq accumulate-start-p nil))))
(null
;; STARTS-WITH is still empty, so we create a new STR object
(setf starts-with
(make-instance 'str
:str ""
:case-insensitive-p (case-insensitive-p str))
;; INITIALIZE-INSTANCE will coerce the STR to a simple
;; string, so we have to fill it afterwards
(slot-value starts-with 'str)
(make-array (len str)
:initial-contents (str str)
:element-type 'character
:fill-pointer t
:adjustable t)
(len starts-with)
(len str)
;; see remark about SKIP above
(skip str) t))
(everything
;; STARTS-WITH already holds an EVERYTHING object - we can't
;; concatenate
(setq accumulate-start-p nil))))
nil)
(declaim (inline convert-aux))
(defun convert-aux (parse-tree)
"Converts the parse tree PARSE-TREE into a REGEX object and returns
it. Will also
- split and optimize repetitions,
- accumulate strings or EVERYTHING objects into the special variable
STARTS-WITH,
- keep track of all registers seen in the special variable REG-NUM,
- keep track of all named registers seen in the special variable REG-NAMES
- keep track of the highest backreference seen in the special
variable MAX-BACK-REF,
- maintain and adher to the currently applicable modifiers in the special
variable FLAGS, and
- maybe even wash your car..."
(declare #.*standard-optimize-settings*)
(if (consp parse-tree)
(convert-compound-parse-tree (first parse-tree) parse-tree)
(convert-simple-parse-tree parse-tree)))
(defgeneric convert-compound-parse-tree (token parse-tree &key)
(declare #.*standard-optimize-settings*)
(:documentation "Helper function for CONVERT-AUX which converts
parse trees which are conses and dispatches on TOKEN which is the
first element of the parse tree.")
(:method ((token t) (parse-tree t) &key)
(signal-syntax-error "Unknown token ~A in parse-tree." token)))
(defmethod convert-compound-parse-tree ((token (eql :sequence)) parse-tree &key)
"The case for parse trees like \(:SEQUENCE {<regex>}*)."
(declare #.*standard-optimize-settings*)
(cond ((cddr parse-tree)
;; this is essentially like
;; (MAPCAR 'CONVERT-AUX (REST PARSE-TREE))
;; but we don't cons a new list
(loop for parse-tree-rest on (rest parse-tree)
while parse-tree-rest
do (setf (car parse-tree-rest)
(convert-aux (car parse-tree-rest))))
(make-instance 'seq :elements (rest parse-tree)))
(t (convert-aux (second parse-tree)))))
(defmethod convert-compound-parse-tree ((token (eql :group)) parse-tree &key)
"The case for parse trees like \(:GROUP {<regex>}*).
This is a syntactical construct equivalent to :SEQUENCE intended to
keep the effect of modifiers local."
(declare #.*standard-optimize-settings*)
(declare (special flags))
;; make a local copy of FLAGS and shadow the global value while we
;; descend into the enclosed regexes
(let ((flags (copy-list flags)))
(declare (special flags))
(cond ((cddr parse-tree)
(loop for parse-tree-rest on (rest parse-tree)
while parse-tree-rest
do (setf (car parse-tree-rest)
(convert-aux (car parse-tree-rest))))
(make-instance 'seq :elements (rest parse-tree)))
(t (convert-aux (second parse-tree))))))
(defmethod convert-compound-parse-tree ((token (eql :alternation)) parse-tree &key)
"The case for \(:ALTERNATION {<regex>}*)."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
;; we must stop accumulating objects into STARTS-WITH once we reach
;; an alternation
(setq accumulate-start-p nil)
(loop for parse-tree-rest on (rest parse-tree)
while parse-tree-rest
do (setf (car parse-tree-rest)
(convert-aux (car parse-tree-rest))))
(make-instance 'alternation :choices (rest parse-tree)))
(defmethod convert-compound-parse-tree ((token (eql :branch)) parse-tree &key)
"The case for \(:BRANCH <test> <regex>).
Here, <test> must be look-ahead, look-behind or number; if <regex> is
an alternation it must have one or two choices."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(let* ((test-candidate (second parse-tree))
(test (cond ((numberp test-candidate)
(when (zerop (the fixnum test-candidate))
(signal-syntax-error "Register 0 doesn't exist: ~S." parse-tree))
(1- (the fixnum test-candidate)))
(t (convert-aux test-candidate))))
(alternations (convert-aux (third parse-tree))))
(when (and (not (numberp test))
(not (typep test 'lookahead))
(not (typep test 'lookbehind)))
(signal-syntax-error "Branch test must be look-ahead, look-behind or number: ~S." parse-tree))
(typecase alternations
(alternation
(case (length (choices alternations))
((0)
(signal-syntax-error "No choices in branch: ~S." parse-tree))
((1)
(make-instance 'branch
:test test
:then-regex (first
(choices alternations))))
((2)
(make-instance 'branch
:test test
:then-regex (first
(choices alternations))
:else-regex (second
(choices alternations))))
(otherwise
(signal-syntax-error "Too much choices in branch: ~S." parse-tree))))
(t
(make-instance 'branch
:test test
:then-regex alternations)))))
(defmethod convert-compound-parse-tree ((token (eql :positive-lookahead)) parse-tree &key)
"The case for \(:POSITIVE-LOOKAHEAD <regex>)."
(declare #.*standard-optimize-settings*)
(declare (special flags accumulate-start-p))
;; keep the effect of modifiers local to the enclosed regex and stop
;; accumulating into STARTS-WITH
(setq accumulate-start-p nil)
(let ((flags (copy-list flags)))
(declare (special flags))
(make-instance 'lookahead
:regex (convert-aux (second parse-tree))
:positivep t)))
(defmethod convert-compound-parse-tree ((token (eql :negative-lookahead)) parse-tree &key)
"The case for \(:NEGATIVE-LOOKAHEAD <regex>)."
(declare #.*standard-optimize-settings*)
;; do the same as for positive look-aheads and just switch afterwards
(let ((regex (convert-compound-parse-tree :positive-lookahead parse-tree)))
(setf (slot-value regex 'positivep) nil)
regex))
(defmethod convert-compound-parse-tree ((token (eql :positive-lookbehind)) parse-tree &key)
"The case for \(:POSITIVE-LOOKBEHIND <regex>)."
(declare #.*standard-optimize-settings*)
(declare (special flags accumulate-start-p))
;; keep the effect of modifiers local to the enclosed regex and stop
;; accumulating into STARTS-WITH
(setq accumulate-start-p nil)
(let* ((flags (copy-list flags))
(regex (convert-aux (second parse-tree)))
(len (regex-length regex)))
(declare (special flags))
;; lookbehind assertions must be of fixed length
(unless len
(signal-syntax-error "Variable length look-behind not implemented \(yet): ~S." parse-tree))
(make-instance 'lookbehind
:regex regex
:positivep t
:len len)))
(defmethod convert-compound-parse-tree ((token (eql :negative-lookbehind)) parse-tree &key)
"The case for \(:NEGATIVE-LOOKBEHIND <regex>)."
(declare #.*standard-optimize-settings*)
;; do the same as for positive look-behinds and just switch afterwards
(let ((regex (convert-compound-parse-tree :positive-lookbehind parse-tree)))
(setf (slot-value regex 'positivep) nil)
regex))
(defmethod convert-compound-parse-tree ((token (eql :greedy-repetition)) parse-tree &key (greedyp t))
"The case for \(:GREEDY-REPETITION|:NON-GREEDY-REPETITION <min> <max> <regex>).
This function is also used for the non-greedy case in which case it is
called with GREEDYP set to NIL as you would expect."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p starts-with))
;; remember the value of ACCUMULATE-START-P upon entering
(let ((local-accumulate-start-p accumulate-start-p))
(let ((minimum (second parse-tree))
(maximum (third parse-tree)))
(declare (fixnum minimum))
(declare (type (or null fixnum) maximum))
(unless (and maximum
(= 1 minimum maximum))
;; set ACCUMULATE-START-P to NIL for the rest of
;; the conversion because we can't continue to
;; accumulate inside as well as after a proper
;; repetition
(setq accumulate-start-p nil))
(let* (reg-seen
(regex (convert-aux (fourth parse-tree)))
(min-len (regex-min-length regex))
(length (regex-length regex)))
;; note that this declaration already applies to
;; the call to CONVERT-AUX above
(declare (special reg-seen))
(when (and local-accumulate-start-p
(not starts-with)
(zerop minimum)
(not maximum))
;; if this repetition is (equivalent to) ".*"
;; and if we're at the start of the regex we
;; remember it for ADVANCE-FN (see the SCAN
;; function)
(setq starts-with (everythingp regex)))
(if (or (not reg-seen)
(not greedyp)
(not length)
(zerop length)
(and maximum (= minimum maximum)))
;; the repetition doesn't enclose a register, or
;; it's not greedy, or we can't determine it's
;; (inner) length, or the length is zero, or the
;; number of repetitions is fixed; in all of
;; these cases we don't bother to optimize
(maybe-split-repetition regex
greedyp
minimum
maximum
min-len
length
reg-seen)
;; otherwise we make a transformation that looks
;; roughly like one of
;; <regex>* -> (?:<regex'>*<regex>)?
;; <regex>+ -> <regex'>*<regex>
;; where the trick is that as much as possible
;; registers from <regex> are removed in
;; <regex'>
(let* (reg-seen ; new instance for REMOVE-REGISTERS
(remove-registers-p t)
(inner-regex (remove-registers regex))
(inner-repetition
;; this is the "<regex'>" part
(maybe-split-repetition inner-regex
;; always greedy
t
;; reduce minimum by 1
;; unless it's already 0
(if (zerop minimum)
0
(1- minimum))
;; reduce maximum by 1
;; unless it's NIL
(and maximum
(1- maximum))
min-len
length
reg-seen))
(inner-seq
;; this is the "<regex'>*<regex>" part
(make-instance 'seq
:elements (list inner-repetition
regex))))
;; note that this declaration already applies
;; to the call to REMOVE-REGISTERS above
(declare (special remove-registers-p reg-seen))
;; wrap INNER-SEQ with a greedy
;; {0,1}-repetition (i.e. "?") if necessary
(if (plusp minimum)
inner-seq
(maybe-split-repetition inner-seq
t
0
1
min-len
nil
t))))))))
(defmethod convert-compound-parse-tree ((token (eql :non-greedy-repetition)) parse-tree &key)
"The case for \(:NON-GREEDY-REPETITION <min> <max> <regex>)."
(declare #.*standard-optimize-settings*)
;; just dispatch to the method above with GREEDYP explicitly set to NIL
(convert-compound-parse-tree :greedy-repetition parse-tree :greedyp nil))
(defmethod convert-compound-parse-tree ((token (eql :register)) parse-tree &key name)
"The case for \(:REGISTER <regex>). Also used for named registers
when NAME is not NIL."
(declare #.*standard-optimize-settings*)
(declare (special flags reg-num reg-names))
;; keep the effect of modifiers local to the enclosed regex; also,
;; assign the current value of REG-NUM to the corresponding slot of
;; the REGISTER object and increase this counter afterwards; for
;; named register update REG-NAMES and set the corresponding name
;; slot of the REGISTER object too
(let ((flags (copy-list flags))
(stored-reg-num reg-num))
(declare (special flags reg-seen named-reg-seen))
(setq reg-seen t)
(when name (setq named-reg-seen t))
(incf (the fixnum reg-num))
(push name reg-names)
(make-instance 'register
:regex (convert-aux (if name (third parse-tree) (second parse-tree)))
:num stored-reg-num
:name name)))
(defmethod convert-compound-parse-tree ((token (eql :named-register)) parse-tree &key)
"The case for \(:NAMED-REGISTER <regex>)."
(declare #.*standard-optimize-settings*)
;; call the method above and use the :NAME keyword argument
(convert-compound-parse-tree :register parse-tree :name (copy-seq (second parse-tree))))
(defmethod convert-compound-parse-tree ((token (eql :filter)) parse-tree &key)
"The case for \(:FILTER <function> &optional <length>)."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
;; stop accumulating into STARTS-WITH
(setq accumulate-start-p nil)
(make-instance 'filter
:fn (second parse-tree)
:len (third parse-tree)))
(defmethod convert-compound-parse-tree ((token (eql :standalone)) parse-tree &key)
"The case for \(:STANDALONE <regex>)."
(declare #.*standard-optimize-settings*)
(declare (special flags accumulate-start-p))
;; stop accumulating into STARTS-WITH
(setq accumulate-start-p nil)
;; keep the effect of modifiers local to the enclosed regex
(let ((flags (copy-list flags)))
(declare (special flags))
(make-instance 'standalone :regex (convert-aux (second parse-tree)))))
(defmethod convert-compound-parse-tree ((token (eql :back-reference)) parse-tree &key)
"The case for \(:BACK-REFERENCE <number>|<name>)."
(declare #.*standard-optimize-settings*)
(declare (special flags accumulate-start-p reg-num reg-names max-back-ref))
(let* ((backref-name (and (stringp (second parse-tree))
(second parse-tree)))
(referred-regs
(when backref-name
;; find which register corresponds to the given name
;; we have to deal with case where several registers share
;; the same name and collect their respective numbers
(loop for name in reg-names
for reg-index from 0
when (string= name backref-name)
;; NOTE: REG-NAMES stores register names in reversed
;; order REG-NUM contains number of (any) registers
;; seen so far; 1- will be done later
collect (- reg-num reg-index))))
;; store the register number for the simple case
(backref-number (or (first referred-regs) (second parse-tree))))
(declare (type (or fixnum null) backref-number))
(when (or (not (typep backref-number 'fixnum))
(<= backref-number 0))
(signal-syntax-error "Illegal back-reference: ~S." parse-tree))
;; stop accumulating into STARTS-WITH and increase MAX-BACK-REF if
;; necessary
(setq accumulate-start-p nil
max-back-ref (max (the fixnum max-back-ref)
backref-number))
(flet ((make-back-ref (backref-number)
(make-instance 'back-reference
;; we start counting from 0 internally
:num (1- backref-number)
:case-insensitive-p (case-insensitive-mode-p flags)
;; backref-name is NIL or string, safe to copy
:name (copy-seq backref-name))))
(cond
((cdr referred-regs)
;; several registers share the same name we will try to match
;; any of them, starting with the most recent first
;; alternation is used to accomplish matching
(make-instance 'alternation
:choices (loop
for reg-index in referred-regs
collect (make-back-ref reg-index))))
;; simple case - backref corresponds to only one register
(t
(make-back-ref backref-number))))))
(defmethod convert-compound-parse-tree ((token (eql :regex)) parse-tree &key)
"The case for \(:REGEX <string>)."
(declare #.*standard-optimize-settings*)
(convert-aux (parse-string (second parse-tree))))
(defmethod convert-compound-parse-tree ((token (eql :char-class)) parse-tree &key invertedp)
"The case for \(:CHAR-CLASS {<item>}*) where item is one of
- a character,
- a character range: \(:RANGE <char1> <char2>), or
- a special char class symbol like :DIGIT-CHAR-CLASS.
Also used for inverted char classes when INVERTEDP is true."
(declare #.*standard-optimize-settings*)
(declare (special flags accumulate-start-p))
(let ((test-function
(create-optimized-test-function
(convert-char-class-to-test-function (rest parse-tree)
invertedp
(case-insensitive-mode-p flags)))))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function test-function)))
(defmethod convert-compound-parse-tree ((token (eql :inverted-char-class)) parse-tree &key)
"The case for \(:INVERTED-CHAR-CLASS {<item>}*)."
(declare #.*standard-optimize-settings*)
;; just dispatch to the "real" method
(convert-compound-parse-tree :char-class parse-tree :invertedp t))
(defmethod convert-compound-parse-tree ((token (eql :property)) parse-tree &key)
"The case for \(:PROPERTY <name>) where <name> is a string."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function (resolve-property (second parse-tree))))
(defmethod convert-compound-parse-tree ((token (eql :inverted-property)) parse-tree &key)
"The case for \(:INVERTED-PROPERTY <name>) where <name> is a string."
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function (complement* (resolve-property (second parse-tree)))))
(defmethod convert-compound-parse-tree ((token (eql :flags)) parse-tree &key)
"The case for \(:FLAGS {<flag>}*) where flag is a modifier symbol
like :CASE-INSENSITIVE-P."
(declare #.*standard-optimize-settings*)
;; set/unset the flags corresponding to the symbols
;; following :FLAGS
(mapc #'set-flag (rest parse-tree))
;; we're only interested in the side effect of
;; setting/unsetting the flags and turn this syntactical
;; construct into a VOID object which'll be optimized
;; away when creating the matcher
(make-instance 'void))
(defgeneric convert-simple-parse-tree (parse-tree)
(declare #.*standard-optimize-settings*)
(:documentation "Helper function for CONVERT-AUX which converts
parse trees which are atoms.")
(:method ((parse-tree (eql :void)))
(declare #.*standard-optimize-settings*)
(make-instance 'void))
(:method ((parse-tree (eql :word-boundary)))
(declare #.*standard-optimize-settings*)
(make-instance 'word-boundary :negatedp nil))
(:method ((parse-tree (eql :non-word-boundary)))
(declare #.*standard-optimize-settings*)
(make-instance 'word-boundary :negatedp t))
(:method ((parse-tree (eql :everything)))
(declare #.*standard-optimize-settings*)
(declare (special flags accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'everything :single-line-p (single-line-mode-p flags)))
(:method ((parse-tree (eql :digit-class)))
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function #'digit-char-p))
(:method ((parse-tree (eql :word-char-class)))
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function #'word-char-p))
(:method ((parse-tree (eql :whitespace-char-class)))
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function #'whitespacep))
(:method ((parse-tree (eql :non-digit-class)))
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function (complement* #'digit-char-p)))
(:method ((parse-tree (eql :non-word-char-class)))
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function (complement* #'word-char-p)))
(:method ((parse-tree (eql :non-whitespace-char-class)))
(declare #.*standard-optimize-settings*)
(declare (special accumulate-start-p))
(setq accumulate-start-p nil)
(make-instance 'char-class :test-function (complement* #'whitespacep)))
(:method ((parse-tree (eql :start-anchor)))
;; Perl's "^"
(declare #.*standard-optimize-settings*)
(declare (special flags))
(make-instance 'anchor :startp t :multi-line-p (multi-line-mode-p flags)))
(:method ((parse-tree (eql :end-anchor)))
;; Perl's "$"
(declare #.*standard-optimize-settings*)
(declare (special flags))
(make-instance 'anchor :startp nil :multi-line-p (multi-line-mode-p flags)))
(:method ((parse-tree (eql :modeless-start-anchor)))
;; Perl's "\A"
(declare #.*standard-optimize-settings*)
(make-instance 'anchor :startp t))
(:method ((parse-tree (eql :modeless-end-anchor)))
;; Perl's "$\Z"
(declare #.*standard-optimize-settings*)
(make-instance 'anchor :startp nil))
(:method ((parse-tree (eql :modeless-end-anchor-no-newline)))
;; Perl's "$\z"
(declare #.*standard-optimize-settings*)
(make-instance 'anchor :startp nil :no-newline-p t))
(:method ((parse-tree (eql :case-insensitive-p)))
(declare #.*standard-optimize-settings*)
(set-flag parse-tree)
(make-instance 'void))
(:method ((parse-tree (eql :case-sensitive-p)))
(declare #.*standard-optimize-settings*)
(set-flag parse-tree)
(make-instance 'void))
(:method ((parse-tree (eql :multi-line-mode-p)))
(declare #.*standard-optimize-settings*)
(set-flag parse-tree)
(make-instance 'void))
(:method ((parse-tree (eql :not-multi-line-mode-p)))
(declare #.*standard-optimize-settings*)
(set-flag parse-tree)
(make-instance 'void))
(:method ((parse-tree (eql :single-line-mode-p)))
(declare #.*standard-optimize-settings*)
(set-flag parse-tree)
(make-instance 'void))
(:method ((parse-tree (eql :not-single-line-mode-p)))
(declare #.*standard-optimize-settings*)
(set-flag parse-tree)
(make-instance 'void)))
(defmethod convert-simple-parse-tree ((parse-tree string))
(declare #.*standard-optimize-settings*)
(declare (special flags))
;; turn strings into STR objects and try to accumulate into
;; STARTS-WITH
(let ((str (make-instance 'str
:str parse-tree
:case-insensitive-p (case-insensitive-mode-p flags))))
(maybe-accumulate str)
str))
(defmethod convert-simple-parse-tree ((parse-tree character))
(declare #.*standard-optimize-settings*)
;; dispatch to the method for strings
(convert-simple-parse-tree (string parse-tree)))
(defmethod convert-simple-parse-tree (parse-tree)
"The default method - check if there's a translation."
(declare #.*standard-optimize-settings*)
(let ((translation (and (symbolp parse-tree) (parse-tree-synonym parse-tree))))
(if translation
(convert-aux (copy-tree translation))
(signal-syntax-error "Unknown token ~A in parse tree." parse-tree))))
(defun convert (parse-tree)
"Converts the parse tree PARSE-TREE into an equivalent REGEX object
and returns three values: the REGEX object, the number of registers
seen and an object the regex starts with which is either a STR object
or an EVERYTHING object \(if the regex starts with something like
\".*\") or NIL."
(declare #.*standard-optimize-settings*)
;; this function basically just initializes the special variables
;; and then calls CONVERT-AUX to do all the work
(let* ((flags (list nil nil nil))
(reg-num 0)
reg-names
named-reg-seen
(accumulate-start-p t)
starts-with
(max-back-ref 0)
(converted-parse-tree (convert-aux parse-tree)))
(declare (special flags reg-num reg-names named-reg-seen
accumulate-start-p starts-with max-back-ref))
;; make sure we don't reference registers which aren't there
(when (> (the fixnum max-back-ref)
(the fixnum reg-num))
(signal-syntax-error "Backreference to register ~A which has not been defined." max-back-ref))
(when (typep starts-with 'str)
(setf (slot-value starts-with 'str)
(coerce (slot-value starts-with 'str)
#+:lispworks 'lw:simple-text-string
#-:lispworks 'simple-string)))
(values converted-parse-tree reg-num starts-with
;; we can't simply use *ALLOW-NAMED-REGISTERS*
;; since parse-tree syntax ignores it
(when named-reg-seen
(nreverse reg-names)))))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,84 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/errors.lisp,v 1.22 2009/09/17 19:17:31 edi Exp $
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defvar *syntax-error-string* nil
"The string which caused the syntax error.")
(define-condition ppcre-error (simple-error)
()
(:documentation "All errors signaled by CL-PPCRE are of
this type."))
(define-condition ppcre-syntax-error (ppcre-error)
((string :initarg :string
:reader ppcre-syntax-error-string)
(pos :initarg :pos
:reader ppcre-syntax-error-pos))
(:default-initargs
:pos nil
:string *syntax-error-string*)
(:report (lambda (condition stream)
(format stream "~?~@[ at position ~A~]~@[ in string ~S~]"
(simple-condition-format-control condition)
(simple-condition-format-arguments condition)
(ppcre-syntax-error-pos condition)
(ppcre-syntax-error-string condition))))
(:documentation "Signaled if CL-PPCRE's parser encounters an error
when trying to parse a regex string or to convert a parse tree into
its internal representation."))
(setf (documentation 'ppcre-syntax-error-string 'function)
"Returns the string the parser was parsing when the error was
encountered \(or NIL if the error happened while trying to convert a
parse tree).")
(setf (documentation 'ppcre-syntax-error-pos 'function)
"Returns the position within the string where the error occurred
\(or NIL if the error happened while trying to convert a parse tree")
(define-condition ppcre-invocation-error (ppcre-error)
()
(:documentation "Signaled when CL-PPCRE functions are
invoked with wrong arguments."))
(defmacro signal-syntax-error* (pos format-control &rest format-arguments)
`(error 'ppcre-syntax-error
:pos ,pos
:format-control ,format-control
:format-arguments (list ,@format-arguments)))
(defmacro signal-syntax-error (format-control &rest format-arguments)
`(signal-syntax-error* nil ,format-control ,@format-arguments))
(defmacro signal-invocation-error (format-control &rest format-arguments)
`(error 'ppcre-invocation-error
:format-control ,format-control
:format-arguments (list ,@format-arguments)))

View file

@ -0,0 +1,738 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/lexer.lisp,v 1.35 2009/09/17 19:17:31 edi Exp $
;;; The lexer's responsibility is to convert the regex string into a
;;; sequence of tokens which are in turn consumed by the parser.
;;;
;;; The lexer is aware of Perl's 'extended mode' and it also 'knows'
;;; (with a little help from the parser) how many register groups it
;;; has opened so far. (The latter is necessary for interpreting
;;; strings like "\\10" correctly.)
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(declaim (inline map-char-to-special-class))
(defun map-char-to-special-char-class (chr)
(declare #.*standard-optimize-settings*)
"Maps escaped characters like \"\\d\" to the tokens which represent
their associated character classes."
(case chr
((#\d)
:digit-class)
((#\D)
:non-digit-class)
((#\w)
:word-char-class)
((#\W)
:non-word-char-class)
((#\s)
:whitespace-char-class)
((#\S)
:non-whitespace-char-class)))
(declaim (inline make-lexer-internal))
(defstruct (lexer (:constructor make-lexer-internal))
"LEXER structures are used to hold the regex string which is
currently lexed and to keep track of the lexer's state."
(str "" :type string :read-only t)
(len 0 :type fixnum :read-only t)
(reg 0 :type fixnum)
(pos 0 :type fixnum)
(last-pos nil :type list))
(defun make-lexer (string)
(declare #-:genera (string string))
(make-lexer-internal :str (maybe-coerce-to-simple-string string)
:len (length string)))
(declaim (inline end-of-string-p))
(defun end-of-string-p (lexer)
(declare #.*standard-optimize-settings*)
"Tests whether we're at the end of the regex string."
(<= (lexer-len lexer)
(lexer-pos lexer)))
(declaim (inline looking-at-p))
(defun looking-at-p (lexer chr)
(declare #.*standard-optimize-settings*)
"Tests whether the next character the lexer would see is CHR.
Does not respect extended mode."
(and (not (end-of-string-p lexer))
(char= (schar (lexer-str lexer) (lexer-pos lexer))
chr)))
(declaim (inline next-char-non-extended))
(defun next-char-non-extended (lexer)
(declare #.*standard-optimize-settings*)
"Returns the next character which is to be examined and updates the
POS slot. Does not respect extended mode."
(cond ((end-of-string-p lexer) nil)
(t (prog1
(schar (lexer-str lexer) (lexer-pos lexer))
(incf (lexer-pos lexer))))))
(defun next-char (lexer)
(declare #.*standard-optimize-settings*)
"Returns the next character which is to be examined and updates the
POS slot. Respects extended mode, i.e. whitespace, comments, and also
nested comments are skipped if applicable."
(let ((next-char (next-char-non-extended lexer))
last-loop-pos)
(loop
;; remember where we started
(setq last-loop-pos (lexer-pos lexer))
;; first we look for nested comments like (?#foo)
(when (and next-char
(char= next-char #\()
(looking-at-p lexer #\?))
(incf (lexer-pos lexer))
(cond ((looking-at-p lexer #\#)
;; must be a nested comment - so we have to search for
;; the closing parenthesis
(let ((error-pos (- (lexer-pos lexer) 2)))
(unless
;; loop 'til ')' or end of regex string and
;; return NIL if ')' wasn't encountered
(loop for skip-char = next-char
then (next-char-non-extended lexer)
while (and skip-char
(char/= skip-char #\)))
finally (return skip-char))
(signal-syntax-error* error-pos "Comment group not closed.")))
(setq next-char (next-char-non-extended lexer)))
(t
;; undo effect of previous INCF if we didn't see a #
(decf (lexer-pos lexer)))))
(when *extended-mode-p*
;; now - if we're in extended mode - we skip whitespace and
;; comments; repeat the following loop while we look at
;; whitespace or #\#
(loop while (and next-char
(or (char= next-char #\#)
(whitespacep next-char)))
do (setq next-char
(if (char= next-char #\#)
;; if we saw a comment marker skip until
;; we're behind #\Newline...
(loop for skip-char = next-char
then (next-char-non-extended lexer)
while (and skip-char
(char/= skip-char #\Newline))
finally (return (next-char-non-extended lexer)))
;; ...otherwise (whitespace) skip until we
;; see the next non-whitespace character
(loop for skip-char = next-char
then (next-char-non-extended lexer)
while (and skip-char
(whitespacep skip-char))
finally (return skip-char))))))
;; if the position has moved we have to repeat our tests
;; because of cases like /^a (?#xxx) (?#yyy) {3}c/x which
;; would be equivalent to /^a{3}c/ in Perl
(unless (> (lexer-pos lexer) last-loop-pos)
(return next-char)))))
(declaim (inline fail))
(defun fail (lexer)
(declare #.*standard-optimize-settings*)
"Moves (LEXER-POS LEXER) back to the last position stored in
\(LEXER-LAST-POS LEXER) and pops the LAST-POS stack."
(unless (lexer-last-pos lexer)
(signal-syntax-error "LAST-POS stack of LEXER ~A is empty." lexer))
(setf (lexer-pos lexer) (pop (lexer-last-pos lexer)))
nil)
(defun get-number (lexer &key (radix 10) max-length no-whitespace-p)
(declare #.*standard-optimize-settings*)
"Read and consume the number the lexer is currently looking at and
return it. Returns NIL if no number could be identified.
RADIX is used as in PARSE-INTEGER. If MAX-LENGTH is not NIL we'll read
at most the next MAX-LENGTH characters. If NO-WHITESPACE-P is not NIL
we don't tolerate whitespace in front of the number."
(when (or (end-of-string-p lexer)
(and no-whitespace-p
(whitespacep (schar (lexer-str lexer) (lexer-pos lexer)))))
(return-from get-number nil))
(multiple-value-bind (integer new-pos)
(parse-integer (lexer-str lexer)
:start (lexer-pos lexer)
:end (if max-length
(let ((end-pos (+ (lexer-pos lexer)
(the fixnum max-length)))
(lexer-len (lexer-len lexer)))
(if (< end-pos lexer-len)
end-pos
lexer-len))
(lexer-len lexer))
:radix radix
:junk-allowed t)
(cond ((and integer (>= (the fixnum integer) 0))
(setf (lexer-pos lexer) new-pos)
integer)
(t nil))))
(declaim (inline try-number))
(defun try-number (lexer &key (radix 10) max-length no-whitespace-p)
(declare #.*standard-optimize-settings*)
"Like GET-NUMBER but won't consume anything if no number is seen."
;; remember current position
(push (lexer-pos lexer) (lexer-last-pos lexer))
(let ((number (get-number lexer
:radix radix
:max-length max-length
:no-whitespace-p no-whitespace-p)))
(or number (fail lexer))))
(declaim (inline make-char-from-code))
(defun make-char-from-code (number error-pos)
(declare #.*standard-optimize-settings*)
"Create character from char-code NUMBER. NUMBER can be NIL
which is interpreted as 0. ERROR-POS is the position where
the corresponding number started within the regex string."
;; only look at rightmost eight bits in compliance with Perl
(let ((code (logand #o377 (the fixnum (or number 0)))))
(or (and (< code char-code-limit)
(code-char code))
(signal-syntax-error* error-pos "No character for hex-code ~X." number))))
(defun unescape-char (lexer)
(declare #.*standard-optimize-settings*)
"Convert the characters\(s) following a backslash into a token
which is returned. This function is to be called when the backslash
has already been consumed. Special character classes like \\W are
handled elsewhere."
(when (end-of-string-p lexer)
(signal-syntax-error "String ends with backslash."))
(let ((chr (next-char-non-extended lexer)))
(case chr
((#\E)
;; if \Q quoting is on this is ignored, otherwise it's just an
;; #\E
(if *allow-quoting*
:void
#\E))
((#\c)
;; \cx means control-x in Perl
(let ((next-char (next-char-non-extended lexer)))
(unless next-char
(signal-syntax-error* (lexer-pos lexer) "Character missing after '\\c'"))
(code-char (logxor #x40 (char-code (char-upcase next-char))))))
((#\x)
;; \x should be followed by a hexadecimal char code,
;; two digits or less
(let* ((error-pos (lexer-pos lexer))
(number (get-number lexer :radix 16 :max-length 2 :no-whitespace-p t)))
;; note that it is OK if \x is followed by zero digits
(make-char-from-code number error-pos)))
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
;; \x should be followed by an octal char code,
;; three digits or less
(let* ((error-pos (decf (lexer-pos lexer)))
(number (get-number lexer :radix 8 :max-length 3)))
(make-char-from-code number error-pos)))
;; the following five character names are 'semi-standard'
;; according to the CLHS but I'm not aware of any implementation
;; that doesn't implement them
((#\t)
#\Tab)
((#\n)
#\Newline)
((#\r)
#\Return)
((#\f)
#\Page)
((#\b)
#\Backspace)
((#\a)
(code-char 7)) ; ASCII bell
((#\e)
(code-char 27)) ; ASCII escape
(otherwise
;; all other characters aren't affected by a backslash
chr))))
(defun read-char-property (lexer first-char)
(declare #.*standard-optimize-settings*)
(unless (eql (next-char-non-extended lexer) #\{)
(signal-syntax-error* (lexer-pos lexer) "Expected left brace after \\~A." first-char))
(let ((name (with-output-to-string (out nil :element-type
#+:lispworks 'lw:simple-char #-:lispworks 'character)
(loop
(let ((char (or (next-char-non-extended lexer)
(signal-syntax-error "Unexpected EOF after \\~A{." first-char))))
(when (char= char #\})
(return))
(write-char char out))))))
(list (if (char= first-char #\p) :property :inverted-property)
name)))
(defun collect-char-class (lexer)
"Reads and consumes characters from regex string until a right
bracket is seen. Assembles them into a list \(which is returned) of
characters, character ranges, like \(:RANGE #\\A #\\E) for a-e, and
tokens representing special character classes."
(declare #.*standard-optimize-settings*)
(let ((start-pos (lexer-pos lexer)) ; remember start for error message
hyphen-seen
last-char
list)
(flet ((handle-char (c)
"Do the right thing with character C depending on whether
we're inside a range or not."
(cond ((and hyphen-seen last-char)
(setf (car list) (list :range last-char c)
last-char nil))
(t
(push c list)
(setq last-char c)))
(setq hyphen-seen nil)))
(loop for first = t then nil
for c = (next-char-non-extended lexer)
;; leave loop if at end of string
while c
do (cond
((char= c #\\)
;; we've seen a backslash
(let ((next-char (next-char-non-extended lexer)))
(case next-char
((#\d #\D #\w #\W #\s #\S)
;; a special character class
(push (map-char-to-special-char-class next-char) list)
;; if the last character was a hyphen
;; just collect it literally
(when hyphen-seen
(push #\- list))
;; if the next character is a hyphen do the same
(when (looking-at-p lexer #\-)
(push #\- list)
(incf (lexer-pos lexer)))
(setq hyphen-seen nil))
((#\P #\p)
;; maybe a character property
(cond ((null *property-resolver*)
(handle-char next-char))
(t
(push (read-char-property lexer next-char) list)
;; if the last character was a hyphen
;; just collect it literally
(when hyphen-seen
(push #\- list))
;; if the next character is a hyphen do the same
(when (looking-at-p lexer #\-)
(push #\- list)
(incf (lexer-pos lexer)))
(setq hyphen-seen nil))))
((#\E)
;; if \Q quoting is on we ignore \E,
;; otherwise it's just a plain #\E
(unless *allow-quoting*
(handle-char #\E)))
(otherwise
;; otherwise unescape the following character(s)
(decf (lexer-pos lexer))
(handle-char (unescape-char lexer))))))
(first
;; the first character must not be a right bracket
;; and isn't treated specially if it's a hyphen
(handle-char c))
((char= c #\])
;; end of character class
;; make sure we collect a pending hyphen
(when hyphen-seen
(setq hyphen-seen nil)
(handle-char #\-))
;; reverse the list to preserve the order intended
;; by the author of the regex string
(return-from collect-char-class (nreverse list)))
((and (char= c #\-)
last-char
(not hyphen-seen))
;; if the last character was 'just a character'
;; we expect to be in the middle of a range
(setq hyphen-seen t))
((char= c #\-)
;; otherwise this is just an ordinary hyphen
(handle-char #\-))
(t
;; default case - just collect the character
(handle-char c))))
;; we can only exit the loop normally if we've reached the end
;; of the regex string without seeing a right bracket
(signal-syntax-error* start-pos "Missing right bracket to close character class."))))
(defun maybe-parse-flags (lexer)
(declare #.*standard-optimize-settings*)
"Reads a sequence of modifiers \(including #\\- to reverse their
meaning) and returns a corresponding list of \"flag\" tokens. The
\"x\" modifier is treated specially in that it dynamically modifies
the behaviour of the lexer itself via the special variable
*EXTENDED-MODE-P*."
(prog1
(loop with set = t
for chr = (next-char-non-extended lexer)
unless chr
do (signal-syntax-error "Unexpected end of string.")
while (find chr "-imsx" :test #'char=)
;; the first #\- will invert the meaning of all modifiers
;; following it
if (char= chr #\-)
do (setq set nil)
else if (char= chr #\x)
do (setq *extended-mode-p* set)
else collect (if set
(case chr
((#\i)
:case-insensitive-p)
((#\m)
:multi-line-mode-p)
((#\s)
:single-line-mode-p))
(case chr
((#\i)
:case-sensitive-p)
((#\m)
:not-multi-line-mode-p)
((#\s)
:not-single-line-mode-p))))
(decf (lexer-pos lexer))))
(defun get-quantifier (lexer)
(declare #.*standard-optimize-settings*)
"Returns a list of two values (min max) if what the lexer is looking
at can be interpreted as a quantifier. Otherwise returns NIL and
resets the lexer to its old position."
;; remember starting position for FAIL and UNGET-TOKEN functions
(push (lexer-pos lexer) (lexer-last-pos lexer))
(let ((next-char (next-char lexer)))
(case next-char
((#\*)
;; * (Kleene star): match 0 or more times
'(0 nil))
((#\+)
;; +: match 1 or more times
'(1 nil))
((#\?)
;; ?: match 0 or 1 times
'(0 1))
((#\{)
;; one of
;; {n}: match exactly n times
;; {n,}: match at least n times
;; {n,m}: match at least n but not more than m times
;; note that anything not matching one of these patterns will
;; be interpreted literally - even whitespace isn't allowed
(let ((num1 (get-number lexer :no-whitespace-p t)))
(if num1
(let ((next-char (next-char-non-extended lexer)))
(case next-char
((#\,)
(let* ((num2 (get-number lexer :no-whitespace-p t))
(next-char (next-char-non-extended lexer)))
(case next-char
((#\})
;; this is the case {n,} (NUM2 is NIL) or {n,m}
(list num1 num2))
(otherwise
(fail lexer)))))
((#\})
;; this is the case {n}
(list num1 num1))
(otherwise
(fail lexer))))
;; no number following left curly brace, so we treat it
;; like a normal character
(fail lexer))))
;; cannot be a quantifier
(otherwise
(fail lexer)))))
(defun parse-register-name-aux (lexer)
"Reads and returns the name in a named register group. It is
assumed that the starting #\< character has already been read. The
closing #\> will also be consumed."
;; we have to look for an ending > character now
(let ((end-name (position #\>
(lexer-str lexer)
:start (lexer-pos lexer)
:test #'char=)))
(unless end-name
;; there has to be > somewhere, syntax error otherwise
(signal-syntax-error* (1- (lexer-pos lexer)) "Opening #\< in named group has no closing #\>."))
(let ((name (subseq (lexer-str lexer)
(lexer-pos lexer)
end-name)))
(unless (every #'(lambda (char)
(or (alphanumericp char)
(char= #\- char)))
name)
;; register name can contain only alphanumeric characters or #\-
(signal-syntax-error* (lexer-pos lexer) "Invalid character in named register group."))
;; advance lexer beyond "<name>" part
(setf (lexer-pos lexer) (1+ end-name))
name)))
(declaim (inline unget-token))
(defun unget-token (lexer)
(declare #.*standard-optimize-settings*)
"Moves the lexer back to the last position stored in the LAST-POS stack."
(if (lexer-last-pos lexer)
(setf (lexer-pos lexer)
(pop (lexer-last-pos lexer)))
(error "No token to unget \(this should not happen)")))
(defun get-token (lexer)
(declare #.*standard-optimize-settings*)
"Returns and consumes the next token from the regex string \(or NIL)."
;; remember starting position for UNGET-TOKEN function
(push (lexer-pos lexer)
(lexer-last-pos lexer))
(let ((next-char (next-char lexer)))
(cond (next-char
(case next-char
;; the easy cases first - the following six characters
;; always have a special meaning and get translated
;; into tokens immediately
((#\))
:close-paren)
((#\|)
:vertical-bar)
((#\?)
:question-mark)
((#\.)
:everything)
((#\^)
:start-anchor)
((#\$)
:end-anchor)
((#\+ #\*)
;; quantifiers will always be consumend by
;; GET-QUANTIFIER, they must not appear here
(signal-syntax-error* (1- (lexer-pos lexer)) "Quantifier '~A' not allowed." next-char))
((#\{)
;; left brace isn't a special character in it's own
;; right but we must check if what follows might
;; look like a quantifier
(let ((this-pos (lexer-pos lexer))
(this-last-pos (lexer-last-pos lexer)))
(unget-token lexer)
(when (get-quantifier lexer)
(signal-syntax-error* (car this-last-pos)
"Quantifier '~A' not allowed."
(subseq (lexer-str lexer)
(car this-last-pos)
(lexer-pos lexer))))
(setf (lexer-pos lexer) this-pos
(lexer-last-pos lexer) this-last-pos)
next-char))
((#\[)
;; left bracket always starts a character class
(cons (cond ((looking-at-p lexer #\^)
(incf (lexer-pos lexer))
:inverted-char-class)
(t
:char-class))
(collect-char-class lexer)))
((#\\)
;; backslash might mean different things so we have
;; to peek one char ahead:
(let ((next-char (next-char-non-extended lexer)))
(case next-char
((#\A)
:modeless-start-anchor)
((#\Z)
:modeless-end-anchor)
((#\z)
:modeless-end-anchor-no-newline)
((#\b)
:word-boundary)
((#\B)
:non-word-boundary)
((#\k)
(cond ((and *allow-named-registers*
(looking-at-p lexer #\<))
;; back-referencing a named register
(incf (lexer-pos lexer))
(list :back-reference
(parse-register-name-aux lexer)))
(t
;; false alarm, just unescape \k
#\k)))
((#\d #\D #\w #\W #\s #\S)
;; these will be treated like character classes
(map-char-to-special-char-class next-char))
((#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
;; uh, a digit...
(let* ((old-pos (decf (lexer-pos lexer)))
;; ...so let's get the whole number first
(backref-number (get-number lexer)))
(declare (fixnum backref-number))
(cond ((and (> backref-number (lexer-reg lexer))
(<= 10 backref-number))
;; \10 and higher are treated as octal
;; character codes if we haven't
;; opened that much register groups
;; yet
(setf (lexer-pos lexer) old-pos)
;; re-read the number from the old
;; position and convert it to its
;; corresponding character
(make-char-from-code (get-number lexer :radix 8 :max-length 3)
old-pos))
(t
;; otherwise this must refer to a
;; backreference
(list :back-reference backref-number)))))
((#\0)
;; this always means an octal character code
;; (at most three digits)
(let ((old-pos (decf (lexer-pos lexer))))
(make-char-from-code (get-number lexer :radix 8 :max-length 3)
old-pos)))
((#\P #\p)
;; might be a named property
(cond (*property-resolver* (read-char-property lexer next-char))
(t next-char)))
(otherwise
;; in all other cases just unescape the
;; character
(decf (lexer-pos lexer))
(unescape-char lexer)))))
((#\()
;; an open parenthesis might mean different things
;; depending on what follows...
(cond ((looking-at-p lexer #\?)
;; this is the case '(?' (and probably more behind)
(incf (lexer-pos lexer))
;; we have to check for modifiers first
;; because a colon might follow
(let* ((flags (maybe-parse-flags lexer))
(next-char (next-char-non-extended lexer)))
;; modifiers are only allowed if a colon
;; or a closing parenthesis are following
(when (and flags
(not (find next-char ":)" :test #'char=)))
(signal-syntax-error* (car (lexer-last-pos lexer))
"Sequence '~A' not recognized."
(subseq (lexer-str lexer)
(car (lexer-last-pos lexer))
(lexer-pos lexer))))
(case next-char
((nil)
;; syntax error
(signal-syntax-error "End of string following '(?'."))
((#\))
;; an empty group except for the flags
;; (if there are any)
(or (and flags
(cons :flags flags))
:void))
((#\()
;; branch
:open-paren-paren)
((#\>)
;; standalone
:open-paren-greater)
((#\=)
;; positive look-ahead
:open-paren-equal)
((#\!)
;; negative look-ahead
:open-paren-exclamation)
((#\:)
;; non-capturing group - return flags as
;; second value
(values :open-paren-colon flags))
((#\<)
;; might be a look-behind assertion or a named group, so
;; check next character
(let ((next-char (next-char-non-extended lexer)))
(cond ((and next-char
(alpha-char-p next-char))
;; we have encountered a named group
;; are we supporting register naming?
(unless *allow-named-registers*
(signal-syntax-error* (1- (lexer-pos lexer))
"Character '~A' may not follow '(?<' (because ~a = NIL)"
next-char
'*allow-named-registers*))
;; put the letter back
(decf (lexer-pos lexer))
;; named group
:open-paren-less-letter)
(t
(case next-char
((#\=)
;; positive look-behind
:open-paren-less-equal)
((#\!)
;; negative look-behind
:open-paren-less-exclamation)
((#\))
;; Perl allows "(?<)" and treats
;; it like a null string
:void)
((nil)
;; syntax error
(signal-syntax-error "End of string following '(?<'."))
(t
;; also syntax error
(signal-syntax-error* (1- (lexer-pos lexer))
"Character '~A' may not follow '(?<'."
next-char )))))))
(otherwise
(signal-syntax-error* (1- (lexer-pos lexer))
"Character '~A' may not follow '(?'."
next-char)))))
(t
;; if next-char was not #\? (this is within
;; the first COND), we've just seen an opening
;; parenthesis and leave it like that
:open-paren)))
(otherwise
;; all other characters are their own tokens
next-char)))
;; we didn't get a character (this if the "else" branch from
;; the first IF), so we don't return a token but NIL
(t
(pop (lexer-last-pos lexer))
nil))))
(declaim (inline start-of-subexpr-p))
(defun start-of-subexpr-p (lexer)
(declare #.*standard-optimize-settings*)
"Tests whether the next token can start a valid sub-expression, i.e.
a stand-alone regex."
(let* ((pos (lexer-pos lexer))
(next-char (next-char lexer)))
(not (or (null next-char)
(prog1
(member (the character next-char)
'(#\) #\|)
:test #'char=)
(setf (lexer-pos lexer) pos))))))

View file

@ -0,0 +1,578 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/optimize.lisp,v 1.36 2009/09/17 19:17:31 edi Exp $
;;; This file contains optimizations which can be applied to converted
;;; parse trees.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defgeneric flatten (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Merges adjacent sequences and alternations, i.e. it
transforms #<SEQ #<STR \"a\"> #<SEQ #<STR \"b\"> #<STR \"c\">>> to
#<SEQ #<STR \"a\"> #<STR \"b\"> #<STR \"c\">>. This is a destructive
operation on REGEX."))
(defmethod flatten ((seq seq))
(declare #.*standard-optimize-settings*)
;; this looks more complicated than it is because we modify SEQ in
;; place to avoid unnecessary consing
(let ((elements-rest (elements seq)))
(loop
(unless elements-rest
(return))
(let ((flattened-element (flatten (car elements-rest)))
(next-elements-rest (cdr elements-rest)))
(cond ((typep flattened-element 'seq)
;; FLATTENED-ELEMENT is a SEQ object, so we "splice"
;; it into out list of elements
(let ((flattened-element-elements
(elements flattened-element)))
(setf (car elements-rest)
(car flattened-element-elements)
(cdr elements-rest)
(nconc (cdr flattened-element-elements)
(cdr elements-rest)))))
(t
;; otherwise we just replace the current element with
;; its flattened counterpart
(setf (car elements-rest) flattened-element)))
(setq elements-rest next-elements-rest))))
(let ((elements (elements seq)))
(cond ((cadr elements)
seq)
((cdr elements)
(first elements))
(t (make-instance 'void)))))
(defmethod flatten ((alternation alternation))
(declare #.*standard-optimize-settings*)
;; same algorithm as above
(let ((choices-rest (choices alternation)))
(loop
(unless choices-rest
(return))
(let ((flattened-choice (flatten (car choices-rest)))
(next-choices-rest (cdr choices-rest)))
(cond ((typep flattened-choice 'alternation)
(let ((flattened-choice-choices
(choices flattened-choice)))
(setf (car choices-rest)
(car flattened-choice-choices)
(cdr choices-rest)
(nconc (cdr flattened-choice-choices)
(cdr choices-rest)))))
(t
(setf (car choices-rest) flattened-choice)))
(setq choices-rest next-choices-rest))))
(let ((choices (choices alternation)))
(cond ((cadr choices)
alternation)
((cdr choices)
(first choices))
(t (signal-syntax-error "Encountered alternation without choices.")))))
(defmethod flatten ((branch branch))
(declare #.*standard-optimize-settings*)
(with-slots (test then-regex else-regex)
branch
(setq test
(if (numberp test)
test
(flatten test))
then-regex (flatten then-regex)
else-regex (flatten else-regex))
branch))
(defmethod flatten ((regex regex))
(declare #.*standard-optimize-settings*)
(typecase regex
((or repetition register lookahead lookbehind standalone)
;; if REGEX contains exactly one inner REGEX object flatten it
(setf (regex regex)
(flatten (regex regex)))
regex)
(t
;; otherwise (ANCHOR, BACK-REFERENCE, CHAR-CLASS, EVERYTHING,
;; LOOKAHEAD, LOOKBEHIND, STR, VOID, FILTER, and WORD-BOUNDARY)
;; do nothing
regex)))
(defgeneric gather-strings (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Collects adjacent strings or characters into one
string provided they have the same case mode. This is a destructive
operation on REGEX."))
(defmethod gather-strings ((seq seq))
(declare #.*standard-optimize-settings*)
;; note that GATHER-STRINGS is to be applied after FLATTEN, i.e. it
;; expects SEQ to be flattened already; in particular, SEQ cannot be
;; empty and cannot contain embedded SEQ objects
(let* ((start-point (cons nil (elements seq)))
(curr-point start-point)
old-case-mode
collector
collector-start
(collector-length 0)
skip)
(declare (fixnum collector-length))
(loop
(let ((elements-rest (cdr curr-point)))
(unless elements-rest
(return))
(let* ((element (car elements-rest))
(case-mode (case-mode element old-case-mode)))
(cond ((and case-mode
(eq case-mode old-case-mode))
;; if ELEMENT is a STR and we have collected a STR of
;; the same case mode in the last iteration we
;; concatenate ELEMENT onto COLLECTOR and remember the
;; value of its SKIP slot
(let ((old-collector-length collector-length))
(unless (and (adjustable-array-p collector)
(array-has-fill-pointer-p collector))
(setq collector
(make-array collector-length
:initial-contents collector
:element-type 'character
:fill-pointer t
:adjustable t)
collector-start nil))
(adjust-array collector
(incf collector-length (len element))
:fill-pointer t)
(setf (subseq collector
old-collector-length)
(str element)
;; it suffices to remember the last SKIP slot
;; because due to the way MAYBE-ACCUMULATE
;; works adjacent STR objects have the same
;; SKIP value
skip (skip element)))
(setf (cdr curr-point) (cdr elements-rest)))
(t
(let ((collected-string
(cond (collector-start
collector-start)
(collector
;; if we have collected something already
;; we convert it into a STR
(make-instance 'str
:skip skip
:str collector
:case-insensitive-p
(eq old-case-mode
:case-insensitive)))
(t nil))))
(cond (case-mode
;; if ELEMENT is a string with a different case
;; mode than the last one we have either just
;; converted COLLECTOR into a STR or COLLECTOR
;; is still empty; in both cases we can now
;; begin to fill it anew
(setq collector (str element)
collector-start element
;; and we remember the SKIP value as above
skip (skip element)
collector-length (len element))
(cond (collected-string
(setf (car elements-rest)
collected-string
curr-point
(cdr curr-point)))
(t
(setf (cdr curr-point)
(cdr elements-rest)))))
(t
;; otherwise this is not a STR so we apply
;; GATHER-STRINGS to it and collect it directly
;; into RESULT
(cond (collected-string
(setf (car elements-rest)
collected-string
curr-point
(cdr curr-point)
(cdr curr-point)
(cons (gather-strings element)
(cdr curr-point))
curr-point
(cdr curr-point)))
(t
(setf (car elements-rest)
(gather-strings element)
curr-point
(cdr curr-point))))
;; we also have to empty COLLECTOR here in case
;; it was still filled from the last iteration
(setq collector nil
collector-start nil))))))
(setq old-case-mode case-mode))))
(when collector
(setf (cdr curr-point)
(cons
(make-instance 'str
:skip skip
:str collector
:case-insensitive-p
(eq old-case-mode
:case-insensitive))
nil)))
(setf (elements seq) (cdr start-point))
seq))
(defmethod gather-strings ((alternation alternation))
(declare #.*standard-optimize-settings*)
;; loop ON the choices of ALTERNATION so we can modify them directly
(loop for choices-rest on (choices alternation)
while choices-rest
do (setf (car choices-rest)
(gather-strings (car choices-rest))))
alternation)
(defmethod gather-strings ((branch branch))
(declare #.*standard-optimize-settings*)
(with-slots (test then-regex else-regex)
branch
(setq test
(if (numberp test)
test
(gather-strings test))
then-regex (gather-strings then-regex)
else-regex (gather-strings else-regex))
branch))
(defmethod gather-strings ((regex regex))
(declare #.*standard-optimize-settings*)
(typecase regex
((or repetition register lookahead lookbehind standalone)
;; if REGEX contains exactly one inner REGEX object apply
;; GATHER-STRINGS to it
(setf (regex regex)
(gather-strings (regex regex)))
regex)
(t
;; otherwise (ANCHOR, BACK-REFERENCE, CHAR-CLASS, EVERYTHING,
;; LOOKAHEAD, LOOKBEHIND, STR, VOID, FILTER, and WORD-BOUNDARY)
;; do nothing
regex)))
;; Note that START-ANCHORED-P will be called after FLATTEN and GATHER-STRINGS.
(defgeneric start-anchored-p (regex &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(:documentation "Returns T if REGEX starts with a \"real\" start
anchor, i.e. one that's not in multi-line mode, NIL otherwise. If
IN-SEQ-P is true the function will return :ZERO-LENGTH if REGEX is a
zero-length assertion."))
(defmethod start-anchored-p ((seq seq) &optional in-seq-p)
(declare (ignore in-seq-p))
;; note that START-ANCHORED-P is to be applied after FLATTEN and
;; GATHER-STRINGS, i.e. SEQ cannot be empty and cannot contain
;; embedded SEQ objects
(loop for element in (elements seq)
for anchored-p = (start-anchored-p element t)
;; skip zero-length elements because they won't affect the
;; "anchoredness" of the sequence
while (eq anchored-p :zero-length)
finally (return (and anchored-p (not (eq anchored-p :zero-length))))))
(defmethod start-anchored-p ((alternation alternation) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(declare (ignore in-seq-p))
;; clearly an alternation can only be start-anchored if all of its
;; choices are start-anchored
(loop for choice in (choices alternation)
always (start-anchored-p choice)))
(defmethod start-anchored-p ((branch branch) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(declare (ignore in-seq-p))
(and (start-anchored-p (then-regex branch))
(start-anchored-p (else-regex branch))))
(defmethod start-anchored-p ((repetition repetition) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(declare (ignore in-seq-p))
;; well, this wouldn't make much sense, but anyway...
(and (plusp (minimum repetition))
(start-anchored-p (regex repetition))))
(defmethod start-anchored-p ((register register) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(declare (ignore in-seq-p))
(start-anchored-p (regex register)))
(defmethod start-anchored-p ((standalone standalone) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(declare (ignore in-seq-p))
(start-anchored-p (regex standalone)))
(defmethod start-anchored-p ((anchor anchor) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(declare (ignore in-seq-p))
(and (startp anchor)
(not (multi-line-p anchor))))
(defmethod start-anchored-p ((regex regex) &optional in-seq-p)
(declare #.*standard-optimize-settings*)
(typecase regex
((or lookahead lookbehind word-boundary void)
;; zero-length assertions
(if in-seq-p
:zero-length
nil))
(filter
(if (and in-seq-p
(len regex)
(zerop (len regex)))
:zero-length
nil))
(t
;; BACK-REFERENCE, CHAR-CLASS, EVERYTHING, and STR
nil)))
;; Note that END-STRING-AUX will be called after FLATTEN and GATHER-STRINGS.
(defgeneric end-string-aux (regex &optional old-case-insensitive-p)
(declare #.*standard-optimize-settings*)
(:documentation "Returns the constant string (if it exists) REGEX
ends with wrapped into a STR object, otherwise NIL.
OLD-CASE-INSENSITIVE-P is the CASE-INSENSITIVE-P slot of the last STR
collected or :VOID if no STR has been collected yet. (This is a helper
function called by END-STRING.)"))
(defmethod end-string-aux ((str str)
&optional (old-case-insensitive-p :void))
(declare #.*standard-optimize-settings*)
(declare (special last-str))
(cond ((and (not (skip str)) ; avoid constituents of STARTS-WITH
;; only use STR if nothing has been collected yet or if
;; the collected string has the same value for
;; CASE-INSENSITIVE-P
(or (eq old-case-insensitive-p :void)
(eq (case-insensitive-p str) old-case-insensitive-p)))
(setf last-str str
;; set the SKIP property of this STR
(skip str) t)
str)
(t nil)))
(defmethod end-string-aux ((seq seq)
&optional (old-case-insensitive-p :void))
(declare #.*standard-optimize-settings*)
(declare (special continuep))
(let (case-insensitive-p
concatenated-string
concatenated-start
(concatenated-length 0))
(declare (fixnum concatenated-length))
(loop for element in (reverse (elements seq))
;; remember the case-(in)sensitivity of the last relevant
;; STR object
for loop-old-case-insensitive-p = old-case-insensitive-p
then (if skip
loop-old-case-insensitive-p
(case-insensitive-p element-end))
;; the end-string of the current element
for element-end = (end-string-aux element
loop-old-case-insensitive-p)
;; whether we encountered a zero-length element
for skip = (if element-end
(zerop (len element-end))
nil)
;; set CONTINUEP to NIL if we have to stop collecting to
;; alert END-STRING-AUX methods on enclosing SEQ objects
unless element-end
do (setq continuep nil)
;; end loop if we neither got a STR nor a zero-length
;; element
while element-end
;; only collect if not zero-length
unless skip
do (cond (concatenated-string
(when concatenated-start
(setf concatenated-string
(make-array concatenated-length
:initial-contents (reverse (str concatenated-start))
:element-type 'character
:fill-pointer t
:adjustable t)
concatenated-start nil))
(let ((len (len element-end))
(str (str element-end)))
(declare (fixnum len))
(incf concatenated-length len)
(loop for i of-type fixnum downfrom (1- len) to 0
do (vector-push-extend (char str i)
concatenated-string))))
(t
(setf concatenated-string
t
concatenated-start
element-end
concatenated-length
(len element-end)
case-insensitive-p
(case-insensitive-p element-end))))
;; stop collecting if END-STRING-AUX on inner SEQ has said so
while continuep)
(cond ((zerop concatenated-length)
;; don't bother to return zero-length strings
nil)
(concatenated-start
concatenated-start)
(t
(make-instance 'str
:str (nreverse concatenated-string)
:case-insensitive-p case-insensitive-p)))))
(defmethod end-string-aux ((register register)
&optional (old-case-insensitive-p :void))
(declare #.*standard-optimize-settings*)
(end-string-aux (regex register) old-case-insensitive-p))
(defmethod end-string-aux ((standalone standalone)
&optional (old-case-insensitive-p :void))
(declare #.*standard-optimize-settings*)
(end-string-aux (regex standalone) old-case-insensitive-p))
(defmethod end-string-aux ((regex regex)
&optional (old-case-insensitive-p :void))
(declare #.*standard-optimize-settings*)
(declare (special last-str end-anchored-p continuep))
(typecase regex
((or anchor lookahead lookbehind word-boundary void)
;; a zero-length REGEX object - for the sake of END-STRING-AUX
;; this is a zero-length string
(when (and (typep regex 'anchor)
(not (startp regex))
(or (no-newline-p regex)
(not (multi-line-p regex)))
(eq old-case-insensitive-p :void))
;; if this is a "real" end-anchor and we haven't collected
;; anything so far we can set END-ANCHORED-P (where 1 or 0
;; indicate whether we accept a #\Newline at the end or not)
(setq end-anchored-p (if (no-newline-p regex) 0 1)))
(make-instance 'str
:str ""
:case-insensitive-p :void))
(t
;; (ALTERNATION, BACK-REFERENCE, BRANCH, CHAR-CLASS, EVERYTHING,
;; REPETITION, FILTER)
nil)))
(defun end-string (regex)
(declare (special end-string-offset))
(declare #.*standard-optimize-settings*)
"Returns the constant string (if it exists) REGEX ends with wrapped
into a STR object, otherwise NIL."
;; LAST-STR points to the last STR object (seen from the end) that's
;; part of END-STRING; CONTINUEP is set to T if we stop collecting
;; in the middle of a SEQ
(let ((continuep t)
last-str)
(declare (special continuep last-str))
(prog1
(end-string-aux regex)
(when last-str
;; if we've found something set the START-OF-END-STRING-P of
;; the leftmost STR collected accordingly and remember the
;; OFFSET of this STR (in a special variable provided by the
;; caller of this function)
(setf (start-of-end-string-p last-str) t
end-string-offset (offset last-str))))))
(defgeneric compute-min-rest (regex current-min-rest)
(declare #.*standard-optimize-settings*)
(:documentation "Returns the minimal length of REGEX plus
CURRENT-MIN-REST. This is similar to REGEX-MIN-LENGTH except that it
recurses down into REGEX and sets the MIN-REST slots of REPETITION
objects."))
(defmethod compute-min-rest ((seq seq) current-min-rest)
(declare #.*standard-optimize-settings*)
(loop for element in (reverse (elements seq))
for last-min-rest = current-min-rest then this-min-rest
for this-min-rest = (compute-min-rest element last-min-rest)
finally (return this-min-rest)))
(defmethod compute-min-rest ((alternation alternation) current-min-rest)
(declare #.*standard-optimize-settings*)
(loop for choice in (choices alternation)
minimize (compute-min-rest choice current-min-rest)))
(defmethod compute-min-rest ((branch branch) current-min-rest)
(declare #.*standard-optimize-settings*)
(min (compute-min-rest (then-regex branch) current-min-rest)
(compute-min-rest (else-regex branch) current-min-rest)))
(defmethod compute-min-rest ((str str) current-min-rest)
(declare #.*standard-optimize-settings*)
(+ current-min-rest (len str)))
(defmethod compute-min-rest ((filter filter) current-min-rest)
(declare #.*standard-optimize-settings*)
(+ current-min-rest (or (len filter) 0)))
(defmethod compute-min-rest ((repetition repetition) current-min-rest)
(declare #.*standard-optimize-settings*)
(setf (min-rest repetition) current-min-rest)
(compute-min-rest (regex repetition) current-min-rest)
(+ current-min-rest (* (minimum repetition) (min-len repetition))))
(defmethod compute-min-rest ((register register) current-min-rest)
(declare #.*standard-optimize-settings*)
(compute-min-rest (regex register) current-min-rest))
(defmethod compute-min-rest ((standalone standalone) current-min-rest)
(declare #.*standard-optimize-settings*)
(declare (ignore current-min-rest))
(compute-min-rest (regex standalone) 0))
(defmethod compute-min-rest ((lookahead lookahead) current-min-rest)
(declare #.*standard-optimize-settings*)
(compute-min-rest (regex lookahead) 0)
current-min-rest)
(defmethod compute-min-rest ((lookbehind lookbehind) current-min-rest)
(declare #.*standard-optimize-settings*)
(compute-min-rest (regex lookbehind) (+ current-min-rest (len lookbehind)))
current-min-rest)
(defmethod compute-min-rest ((regex regex) current-min-rest)
(declare #.*standard-optimize-settings*)
(typecase regex
((or char-class everything)
(1+ current-min-rest))
(t
;; zero min-len and no embedded regexes (ANCHOR,
;; BACK-REFERENCE, VOID, and WORD-BOUNDARY)
current-min-rest)))

View file

@ -0,0 +1,69 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/packages.lisp,v 1.39 2009/09/17 19:17:31 edi Exp $
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-user)
(defpackage :cl-ppcre
(:nicknames :ppcre)
#+:genera
(:shadowing-import-from :common-lisp :lambda :simple-string :string)
(:use #-:genera :cl #+:genera :future-common-lisp)
(:shadow :digit-char-p :defconstant)
(:export :parse-string
:create-scanner
:create-optimized-test-function
:parse-tree-synonym
:define-parse-tree-synonym
:scan
:scan-to-strings
:do-scans
:do-matches
:do-matches-as-strings
:all-matches
:all-matches-as-strings
:split
:regex-replace
:regex-replace-all
:regex-apropos
:regex-apropos-list
:quote-meta-chars
:*regex-char-code-limit*
:*use-bmh-matchers*
:*allow-quoting*
:*allow-named-registers*
:*optimize-char-classes*
:*property-resolver*
:*look-ahead-for-suffix*
:ppcre-error
:ppcre-invocation-error
:ppcre-syntax-error
:ppcre-syntax-error-string
:ppcre-syntax-error-pos
:register-groups-bind
:do-register-groups))

View file

@ -0,0 +1,290 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/parser.lisp,v 1.31 2009/09/17 19:17:31 edi Exp $
;;; The parser will - with the help of the lexer - parse a regex
;;; string and convert it into a "parse tree" (see docs for details
;;; about the syntax of these trees). Note that the lexer might
;;; return illegal parse trees. It is assumed that the conversion
;;; process later on will track them down.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defun group (lexer)
"Parses and consumes a <group>.
The productions are: <group> -> \"\(\"<regex>\")\"
\"\(?:\"<regex>\")\"
\"\(?>\"<regex>\")\"
\"\(?<flags>:\"<regex>\")\"
\"\(?=\"<regex>\")\"
\"\(?!\"<regex>\")\"
\"\(?<=\"<regex>\")\"
\"\(?<!\"<regex>\")\"
\"\(?\(\"<num>\")\"<regex>\")\"
\"\(?\(\"<regex>\")\"<regex>\")\"
\"\(?<name>\"<regex>\")\" \(when *ALLOW-NAMED-REGISTERS* is T)
<legal-token>
where <flags> is parsed by the lexer function MAYBE-PARSE-FLAGS.
Will return <parse-tree> or \(<grouping-type> <parse-tree>) where
<grouping-type> is one of six keywords - see source for details."
(declare #.*standard-optimize-settings*)
(multiple-value-bind (open-token flags)
(get-token lexer)
(cond ((eq open-token :open-paren-paren)
;; special case for conditional regular expressions; note
;; that at this point we accept a couple of illegal
;; combinations which'll be sorted out later by the
;; converter
(let* ((open-paren-pos (car (lexer-last-pos lexer)))
;; check if what follows "(?(" is a number
(number (try-number lexer :no-whitespace-p t))
;; make changes to extended-mode-p local
(*extended-mode-p* *extended-mode-p*))
(declare (fixnum open-paren-pos))
(cond (number
;; condition is a number (i.e. refers to a
;; back-reference)
(let* ((inner-close-token (get-token lexer))
(reg-expr (reg-expr lexer))
(close-token (get-token lexer)))
(unless (eq inner-close-token :close-paren)
(signal-syntax-error* (+ open-paren-pos 2)
"Opening paren has no matching closing paren."))
(unless (eq close-token :close-paren)
(signal-syntax-error* open-paren-pos
"Opening paren has no matching closing paren."))
(list :branch number reg-expr)))
(t
;; condition must be a full regex (actually a
;; look-behind or look-ahead); and here comes a
;; terrible kludge: instead of being cleanly
;; separated from the lexer, the parser pushes
;; back the lexer by one position, thereby
;; landing in the middle of the 'token' "(?(" -
;; yuck!!
(decf (lexer-pos lexer))
(let* ((inner-reg-expr (group lexer))
(reg-expr (reg-expr lexer))
(close-token (get-token lexer)))
(unless (eq close-token :close-paren)
(signal-syntax-error* open-paren-pos
"Opening paren has no matching closing paren."))
(list :branch inner-reg-expr reg-expr))))))
((member open-token '(:open-paren
:open-paren-colon
:open-paren-greater
:open-paren-equal
:open-paren-exclamation
:open-paren-less-equal
:open-paren-less-exclamation
:open-paren-less-letter)
:test #'eq)
;; make changes to extended-mode-p local
(let ((*extended-mode-p* *extended-mode-p*))
;; we saw one of the six token representing opening
;; parentheses
(let* ((open-paren-pos (car (lexer-last-pos lexer)))
(register-name (when (eq open-token :open-paren-less-letter)
(parse-register-name-aux lexer)))
(reg-expr (reg-expr lexer))
(close-token (get-token lexer)))
(when (or (eq open-token :open-paren)
(eq open-token :open-paren-less-letter))
;; if this is the "("<regex>")" or "(?"<name>""<regex>")" production we have to
;; increment the register counter of the lexer
(incf (lexer-reg lexer)))
(unless (eq close-token :close-paren)
;; the token following <regex> must be the closing
;; parenthesis or this is a syntax error
(signal-syntax-error* open-paren-pos
"Opening paren has no matching closing paren."))
(if flags
;; if the lexer has returned a list of flags this must
;; have been the "(?:"<regex>")" production
(cons :group (nconc flags (list reg-expr)))
(if (eq open-token :open-paren-less-letter)
(list :named-register register-name
reg-expr)
(list (case open-token
((:open-paren)
:register)
((:open-paren-colon)
:group)
((:open-paren-greater)
:standalone)
((:open-paren-equal)
:positive-lookahead)
((:open-paren-exclamation)
:negative-lookahead)
((:open-paren-less-equal)
:positive-lookbehind)
((:open-paren-less-exclamation)
:negative-lookbehind))
reg-expr))))))
(t
;; this is the <legal-token> production; <legal-token> is
;; any token which passes START-OF-SUBEXPR-P (otherwise
;; parsing had already stopped in the SEQ method)
open-token))))
(defun greedy-quant (lexer)
"Parses and consumes a <greedy-quant>.
The productions are: <greedy-quant> -> <group> | <group><quantifier>
where <quantifier> is parsed by the lexer function GET-QUANTIFIER.
Will return <parse-tree> or (:GREEDY-REPETITION <min> <max> <parse-tree>)."
(declare #.*standard-optimize-settings*)
(let* ((group (group lexer))
(token (get-quantifier lexer)))
(if token
;; if GET-QUANTIFIER returned a non-NIL value it's the
;; two-element list (<min> <max>)
(list :greedy-repetition (first token) (second token) group)
group)))
(defun quant (lexer)
"Parses and consumes a <quant>.
The productions are: <quant> -> <greedy-quant> | <greedy-quant>\"?\".
Will return the <parse-tree> returned by GREEDY-QUANT and optionally
change :GREEDY-REPETITION to :NON-GREEDY-REPETITION."
(declare #.*standard-optimize-settings*)
(let* ((greedy-quant (greedy-quant lexer))
(pos (lexer-pos lexer))
(next-char (next-char lexer)))
(when next-char
(if (char= next-char #\?)
(setf (car greedy-quant) :non-greedy-repetition)
(setf (lexer-pos lexer) pos)))
greedy-quant))
(defun seq (lexer)
"Parses and consumes a <seq>.
The productions are: <seq> -> <quant> | <quant><seq>.
Will return <parse-tree> or (:SEQUENCE <parse-tree> <parse-tree>)."
(declare #.*standard-optimize-settings*)
(flet ((make-array-from-two-chars (char1 char2)
(let ((string (make-array 2
:element-type 'character
:fill-pointer t
:adjustable t)))
(setf (aref string 0) char1)
(setf (aref string 1) char2)
string)))
;; Note that we're calling START-OF-SUBEXPR-P before we actually try
;; to parse a <seq> or <quant> in order to catch empty regular
;; expressions
(if (start-of-subexpr-p lexer)
(loop with seq-is-sequence-p = nil
with last-cdr
for quant = (quant lexer)
for quant-is-char-p = (characterp quant)
for seq = quant
then
(cond ((and quant-is-char-p (characterp seq))
(make-array-from-two-chars seq quant))
((and quant-is-char-p (stringp seq))
(vector-push-extend quant seq)
seq)
((not seq-is-sequence-p)
(setf last-cdr (list quant)
seq-is-sequence-p t)
(list* :sequence seq last-cdr))
((and quant-is-char-p
(characterp (car last-cdr)))
(setf (car last-cdr)
(make-array-from-two-chars (car last-cdr)
quant))
seq)
((and quant-is-char-p
(stringp (car last-cdr)))
(vector-push-extend quant (car last-cdr))
seq)
(t
;; if <seq> is also a :SEQUENCE parse tree we merge
;; both lists into one
(let ((cons (list quant)))
(psetf last-cdr cons
(cdr last-cdr) cons))
seq))
while (start-of-subexpr-p lexer)
finally (return seq))
:void)))
(defun reg-expr (lexer)
"Parses and consumes a <regex>, a complete regular expression.
The productions are: <regex> -> <seq> | <seq>\"|\"<regex>.
Will return <parse-tree> or (:ALTERNATION <parse-tree> <parse-tree>)."
(declare #.*standard-optimize-settings*)
(let ((pos (lexer-pos lexer)))
(case (next-char lexer)
((nil)
;; if we didn't get any token we return :VOID which stands for
;; "empty regular expression"
:void)
((#\|)
;; now check whether the expression started with a vertical
;; bar, i.e. <seq> - the left alternation - is empty
(list :alternation :void (reg-expr lexer)))
(otherwise
;; otherwise un-read the character we just saw and parse a
;; <seq> plus the character following it
(setf (lexer-pos lexer) pos)
(let* ((seq (seq lexer))
(pos (lexer-pos lexer)))
(case (next-char lexer)
((nil)
;; no further character, just a <seq>
seq)
((#\|)
;; if the character was a vertical bar, this is an
;; alternation and we have the second production
(let ((reg-expr (reg-expr lexer)))
(cond ((and (consp reg-expr)
(eq (first reg-expr) :alternation))
;; again we try to merge as above in SEQ
(setf (cdr reg-expr)
(cons seq (cdr reg-expr)))
reg-expr)
(t (list :alternation seq reg-expr)))))
(otherwise
;; a character which is not a vertical bar - this is
;; either a syntax error or we're inside of a group and
;; the next character is a closing parenthesis; so we
;; just un-read the character and let another function
;; take care of it
(setf (lexer-pos lexer) pos)
seq)))))))
(defun parse-string (string)
"Translate the regex string STRING into a parse tree."
(declare #.*standard-optimize-settings*)
(let* ((lexer (make-lexer string))
(parse-tree (reg-expr lexer)))
;; check whether we've consumed the whole regex string
(if (end-of-string-p lexer)
parse-tree
(signal-syntax-error* (lexer-pos lexer) "Expected end of string."))))

View file

@ -0,0 +1,555 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/regex-class-util.lisp,v 1.9 2009/09/17 19:17:31 edi Exp $
;;; This file contains some utility methods for REGEX objects.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
;;; The following four methods allow a VOID object to behave like a
;;; zero-length STR object (only readers needed)
(defmethod len ((void void))
(declare #.*standard-optimize-settings*)
0)
(defmethod str ((void void))
(declare #.*standard-optimize-settings*)
"")
(defmethod skip ((void void))
(declare #.*standard-optimize-settings*)
nil)
(defmethod start-of-end-string-p ((void void))
(declare #.*standard-optimize-settings*)
nil)
(defgeneric case-mode (regex old-case-mode)
(declare #.*standard-optimize-settings*)
(:documentation "Utility function used by the optimizer (see GATHER-STRINGS).
Returns a keyword denoting the case-(in)sensitivity of a STR or its
second argument if the STR has length 0. Returns NIL for REGEX objects
which are not of type STR."))
(defmethod case-mode ((str str) old-case-mode)
(declare #.*standard-optimize-settings*)
(cond ((zerop (len str))
old-case-mode)
((case-insensitive-p str)
:case-insensitive)
(t
:case-sensitive)))
(defmethod case-mode ((regex regex) old-case-mode)
(declare #.*standard-optimize-settings*)
(declare (ignore old-case-mode))
nil)
(defgeneric copy-regex (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Implements a deep copy of a REGEX object."))
(defmethod copy-regex ((anchor anchor))
(declare #.*standard-optimize-settings*)
(make-instance 'anchor
:startp (startp anchor)
:multi-line-p (multi-line-p anchor)
:no-newline-p (no-newline-p anchor)))
(defmethod copy-regex ((everything everything))
(declare #.*standard-optimize-settings*)
(make-instance 'everything
:single-line-p (single-line-p everything)))
(defmethod copy-regex ((word-boundary word-boundary))
(declare #.*standard-optimize-settings*)
(make-instance 'word-boundary
:negatedp (negatedp word-boundary)))
(defmethod copy-regex ((void void))
(declare #.*standard-optimize-settings*)
(make-instance 'void))
(defmethod copy-regex ((lookahead lookahead))
(declare #.*standard-optimize-settings*)
(make-instance 'lookahead
:regex (copy-regex (regex lookahead))
:positivep (positivep lookahead)))
(defmethod copy-regex ((seq seq))
(declare #.*standard-optimize-settings*)
(make-instance 'seq
:elements (mapcar #'copy-regex (elements seq))))
(defmethod copy-regex ((alternation alternation))
(declare #.*standard-optimize-settings*)
(make-instance 'alternation
:choices (mapcar #'copy-regex (choices alternation))))
(defmethod copy-regex ((branch branch))
(declare #.*standard-optimize-settings*)
(with-slots (test)
branch
(make-instance 'branch
:test (if (typep test 'regex)
(copy-regex test)
test)
:then-regex (copy-regex (then-regex branch))
:else-regex (copy-regex (else-regex branch)))))
(defmethod copy-regex ((lookbehind lookbehind))
(declare #.*standard-optimize-settings*)
(make-instance 'lookbehind
:regex (copy-regex (regex lookbehind))
:positivep (positivep lookbehind)
:len (len lookbehind)))
(defmethod copy-regex ((repetition repetition))
(declare #.*standard-optimize-settings*)
(make-instance 'repetition
:regex (copy-regex (regex repetition))
:greedyp (greedyp repetition)
:minimum (minimum repetition)
:maximum (maximum repetition)
:min-len (min-len repetition)
:len (len repetition)
:contains-register-p (contains-register-p repetition)))
(defmethod copy-regex ((register register))
(declare #.*standard-optimize-settings*)
(make-instance 'register
:regex (copy-regex (regex register))
:num (num register)
:name (name register)))
(defmethod copy-regex ((standalone standalone))
(declare #.*standard-optimize-settings*)
(make-instance 'standalone
:regex (copy-regex (regex standalone))))
(defmethod copy-regex ((back-reference back-reference))
(declare #.*standard-optimize-settings*)
(make-instance 'back-reference
:num (num back-reference)
:case-insensitive-p (case-insensitive-p back-reference)))
(defmethod copy-regex ((char-class char-class))
(declare #.*standard-optimize-settings*)
(make-instance 'char-class
:test-function (test-function char-class)))
(defmethod copy-regex ((str str))
(declare #.*standard-optimize-settings*)
(make-instance 'str
:str (str str)
:case-insensitive-p (case-insensitive-p str)))
(defmethod copy-regex ((filter filter))
(declare #.*standard-optimize-settings*)
(make-instance 'filter
:fn (fn filter)
:len (len filter)))
;;; Note that COPY-REGEX and REMOVE-REGISTERS could have easily been
;;; wrapped into one function. Maybe in the next release...
;;; Further note that this function is used by CONVERT to factor out
;;; complicated repetitions, i.e. cases like
;;; (a)* -> (?:a*(a))?
;;; This won't work for, say,
;;; ((a)|(b))* -> (?:(?:a|b)*((a)|(b)))?
;;; and therefore we stop REGISTER removal once we see an ALTERNATION.
(defgeneric remove-registers (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Returns a deep copy of a REGEX (see COPY-REGEX) and
optionally removes embedded REGISTER objects if possible and if the
special variable REMOVE-REGISTERS-P is true."))
(defmethod remove-registers ((register register))
(declare #.*standard-optimize-settings*)
(declare (special remove-registers-p reg-seen))
(cond (remove-registers-p
(remove-registers (regex register)))
(t
;; mark REG-SEEN as true so enclosing REPETITION objects
;; (see method below) know if they contain a register or not
(setq reg-seen t)
(copy-regex register))))
(defmethod remove-registers ((repetition repetition))
(declare #.*standard-optimize-settings*)
(let* (reg-seen
(inner-regex (remove-registers (regex repetition))))
;; REMOVE-REGISTERS will set REG-SEEN (see method above) if
;; (REGEX REPETITION) contains a REGISTER
(declare (special reg-seen))
(make-instance 'repetition
:regex inner-regex
:greedyp (greedyp repetition)
:minimum (minimum repetition)
:maximum (maximum repetition)
:min-len (min-len repetition)
:len (len repetition)
:contains-register-p reg-seen)))
(defmethod remove-registers ((standalone standalone))
(declare #.*standard-optimize-settings*)
(make-instance 'standalone
:regex (remove-registers (regex standalone))))
(defmethod remove-registers ((lookahead lookahead))
(declare #.*standard-optimize-settings*)
(make-instance 'lookahead
:regex (remove-registers (regex lookahead))
:positivep (positivep lookahead)))
(defmethod remove-registers ((lookbehind lookbehind))
(declare #.*standard-optimize-settings*)
(make-instance 'lookbehind
:regex (remove-registers (regex lookbehind))
:positivep (positivep lookbehind)
:len (len lookbehind)))
(defmethod remove-registers ((branch branch))
(declare #.*standard-optimize-settings*)
(with-slots (test)
branch
(make-instance 'branch
:test (if (typep test 'regex)
(remove-registers test)
test)
:then-regex (remove-registers (then-regex branch))
:else-regex (remove-registers (else-regex branch)))))
(defmethod remove-registers ((alternation alternation))
(declare #.*standard-optimize-settings*)
(declare (special remove-registers-p))
;; an ALTERNATION, so we can't remove REGISTER objects further down
(setq remove-registers-p nil)
(copy-regex alternation))
(defmethod remove-registers ((regex regex))
(declare #.*standard-optimize-settings*)
(copy-regex regex))
(defmethod remove-registers ((seq seq))
(declare #.*standard-optimize-settings*)
(make-instance 'seq
:elements (mapcar #'remove-registers (elements seq))))
(defgeneric everythingp (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Returns an EVERYTHING object if REGEX is equivalent
to this object, otherwise NIL. So, \"(.){1}\" would return true
\(i.e. the object corresponding to \".\", for example."))
(defmethod everythingp ((seq seq))
(declare #.*standard-optimize-settings*)
;; we might have degenerate cases like (:SEQUENCE :VOID ...)
;; due to the parsing process
(let ((cleaned-elements (remove-if #'(lambda (element)
(typep element 'void))
(elements seq))))
(and (= 1 (length cleaned-elements))
(everythingp (first cleaned-elements)))))
(defmethod everythingp ((alternation alternation))
(declare #.*standard-optimize-settings*)
(with-slots (choices)
alternation
(and (= 1 (length choices))
;; this is unlikely to happen for human-generated regexes,
;; but machine-generated ones might look like this
(everythingp (first choices)))))
(defmethod everythingp ((repetition repetition))
(declare #.*standard-optimize-settings*)
(with-slots (maximum minimum regex)
repetition
(and maximum
(= 1 minimum maximum)
;; treat "<regex>{1,1}" like "<regex>"
(everythingp regex))))
(defmethod everythingp ((register register))
(declare #.*standard-optimize-settings*)
(everythingp (regex register)))
(defmethod everythingp ((standalone standalone))
(declare #.*standard-optimize-settings*)
(everythingp (regex standalone)))
(defmethod everythingp ((everything everything))
(declare #.*standard-optimize-settings*)
everything)
(defmethod everythingp ((regex regex))
(declare #.*standard-optimize-settings*)
;; the general case for ANCHOR, BACK-REFERENCE, BRANCH, CHAR-CLASS,
;; LOOKAHEAD, LOOKBEHIND, STR, VOID, FILTER, and WORD-BOUNDARY
nil)
(defgeneric regex-length (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Return the length of REGEX if it is fixed, NIL otherwise."))
(defmethod regex-length ((seq seq))
(declare #.*standard-optimize-settings*)
;; simply add all inner lengths unless one of them is NIL
(loop for sub-regex in (elements seq)
for len = (regex-length sub-regex)
if (not len) do (return nil)
sum len))
(defmethod regex-length ((alternation alternation))
(declare #.*standard-optimize-settings*)
;; only return a true value if all inner lengths are non-NIL and
;; mutually equal
(loop for sub-regex in (choices alternation)
for old-len = nil then len
for len = (regex-length sub-regex)
if (or (not len)
(and old-len (/= len old-len))) do (return nil)
finally (return len)))
(defmethod regex-length ((branch branch))
(declare #.*standard-optimize-settings*)
;; only return a true value if both alternations have a length and
;; if they're equal
(let ((then-length (regex-length (then-regex branch))))
(and then-length
(eql then-length (regex-length (else-regex branch)))
then-length)))
(defmethod regex-length ((repetition repetition))
(declare #.*standard-optimize-settings*)
;; we can only compute the length of a REPETITION object if the
;; number of repetitions is fixed; note that we don't call
;; REGEX-LENGTH for the inner regex, we assume that the LEN slot is
;; always set correctly
(with-slots (len minimum maximum)
repetition
(if (and len
(eql minimum maximum))
(* minimum len)
nil)))
(defmethod regex-length ((register register))
(declare #.*standard-optimize-settings*)
(regex-length (regex register)))
(defmethod regex-length ((standalone standalone))
(declare #.*standard-optimize-settings*)
(regex-length (regex standalone)))
(defmethod regex-length ((back-reference back-reference))
(declare #.*standard-optimize-settings*)
;; with enough effort we could possibly do better here, but
;; currently we just give up and return NIL
nil)
(defmethod regex-length ((char-class char-class))
(declare #.*standard-optimize-settings*)
1)
(defmethod regex-length ((everything everything))
(declare #.*standard-optimize-settings*)
1)
(defmethod regex-length ((str str))
(declare #.*standard-optimize-settings*)
(len str))
(defmethod regex-length ((filter filter))
(declare #.*standard-optimize-settings*)
(len filter))
(defmethod regex-length ((regex regex))
(declare #.*standard-optimize-settings*)
;; the general case for ANCHOR, LOOKAHEAD, LOOKBEHIND, VOID, and
;; WORD-BOUNDARY (which all have zero-length)
0)
(defgeneric regex-min-length (regex)
(declare #.*standard-optimize-settings*)
(:documentation "Returns the minimal length of REGEX."))
(defmethod regex-min-length ((seq seq))
(declare #.*standard-optimize-settings*)
;; simply add all inner minimal lengths
(loop for sub-regex in (elements seq)
for len = (regex-min-length sub-regex)
sum len))
(defmethod regex-min-length ((alternation alternation))
(declare #.*standard-optimize-settings*)
;; minimal length of an alternation is the minimal length of the
;; "shortest" element
(loop for sub-regex in (choices alternation)
for len = (regex-min-length sub-regex)
minimize len))
(defmethod regex-min-length ((branch branch))
(declare #.*standard-optimize-settings*)
;; minimal length of both alternations
(min (regex-min-length (then-regex branch))
(regex-min-length (else-regex branch))))
(defmethod regex-min-length ((repetition repetition))
(declare #.*standard-optimize-settings*)
;; obviously the product of the inner minimal length and the minimal
;; number of repetitions
(* (minimum repetition) (min-len repetition)))
(defmethod regex-min-length ((register register))
(declare #.*standard-optimize-settings*)
(regex-min-length (regex register)))
(defmethod regex-min-length ((standalone standalone))
(declare #.*standard-optimize-settings*)
(regex-min-length (regex standalone)))
(defmethod regex-min-length ((char-class char-class))
(declare #.*standard-optimize-settings*)
1)
(defmethod regex-min-length ((everything everything))
(declare #.*standard-optimize-settings*)
1)
(defmethod regex-min-length ((str str))
(declare #.*standard-optimize-settings*)
(len str))
(defmethod regex-min-length ((filter filter))
(declare #.*standard-optimize-settings*)
(or (len filter)
0))
(defmethod regex-min-length ((regex regex))
(declare #.*standard-optimize-settings*)
;; the general case for ANCHOR, BACK-REFERENCE, LOOKAHEAD,
;; LOOKBEHIND, VOID, and WORD-BOUNDARY
0)
(defgeneric compute-offsets (regex start-pos)
(declare #.*standard-optimize-settings*)
(:documentation "Returns the offset the following regex would have
relative to START-POS or NIL if we can't compute it. Sets the OFFSET
slot of REGEX to START-POS if REGEX is a STR. May also affect OFFSET
slots of STR objects further down the tree."))
;; note that we're actually only interested in the offset of
;; "top-level" STR objects (see ADVANCE-FN in the SCAN function) so we
;; can stop at variable-length alternations and don't need to descend
;; into repetitions
(defmethod compute-offsets ((seq seq) start-pos)
(declare #.*standard-optimize-settings*)
(loop for element in (elements seq)
;; advance offset argument for next call while looping through
;; the elements
for pos = start-pos then curr-offset
for curr-offset = (compute-offsets element pos)
while curr-offset
finally (return curr-offset)))
(defmethod compute-offsets ((alternation alternation) start-pos)
(declare #.*standard-optimize-settings*)
(loop for choice in (choices alternation)
for old-offset = nil then curr-offset
for curr-offset = (compute-offsets choice start-pos)
;; we stop immediately if two alternations don't result in the
;; same offset
if (or (not curr-offset)
(and old-offset (/= curr-offset old-offset)))
do (return nil)
finally (return curr-offset)))
(defmethod compute-offsets ((branch branch) start-pos)
(declare #.*standard-optimize-settings*)
;; only return offset if both alternations have equal value
(let ((then-offset (compute-offsets (then-regex branch) start-pos)))
(and then-offset
(eql then-offset (compute-offsets (else-regex branch) start-pos))
then-offset)))
(defmethod compute-offsets ((repetition repetition) start-pos)
(declare #.*standard-optimize-settings*)
;; no need to descend into the inner regex
(with-slots (len minimum maximum)
repetition
(if (and len
(eq minimum maximum))
;; fixed number of repetitions, so we know how to proceed
(+ start-pos (* minimum len))
;; otherwise return NIL
nil)))
(defmethod compute-offsets ((register register) start-pos)
(declare #.*standard-optimize-settings*)
(compute-offsets (regex register) start-pos))
(defmethod compute-offsets ((standalone standalone) start-pos)
(declare #.*standard-optimize-settings*)
(compute-offsets (regex standalone) start-pos))
(defmethod compute-offsets ((char-class char-class) start-pos)
(declare #.*standard-optimize-settings*)
(1+ start-pos))
(defmethod compute-offsets ((everything everything) start-pos)
(declare #.*standard-optimize-settings*)
(1+ start-pos))
(defmethod compute-offsets ((str str) start-pos)
(declare #.*standard-optimize-settings*)
(setf (offset str) start-pos)
(+ start-pos (len str)))
(defmethod compute-offsets ((back-reference back-reference) start-pos)
(declare #.*standard-optimize-settings*)
;; with enough effort we could possibly do better here, but
;; currently we just give up and return NIL
(declare (ignore start-pos))
nil)
(defmethod compute-offsets ((filter filter) start-pos)
(declare #.*standard-optimize-settings*)
(let ((len (len filter)))
(if len
(+ start-pos len)
nil)))
(defmethod compute-offsets ((regex regex) start-pos)
(declare #.*standard-optimize-settings*)
;; the general case for ANCHOR, LOOKAHEAD, LOOKBEHIND, VOID, and
;; WORD-BOUNDARY (which all have zero-length)
start-pos)

View file

@ -0,0 +1,271 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/regex-class.lisp,v 1.44 2009/10/28 07:36:15 edi Exp $
;;; This file defines the REGEX class. REGEX objects are used to
;;; represent the (transformed) parse trees internally
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defclass regex ()
()
(:documentation "The REGEX base class. All other classes inherit
from this one."))
(defclass seq (regex)
((elements :initarg :elements
:accessor elements
:type cons
:documentation "A list of REGEX objects."))
(:documentation "SEQ objects represents sequences of regexes.
\(Like \"ab\" is the sequence of \"a\" and \"b\".)"))
(defclass alternation (regex)
((choices :initarg :choices
:accessor choices
:type cons
:documentation "A list of REGEX objects"))
(:documentation "ALTERNATION objects represent alternations of
regexes. \(Like \"a|b\" ist the alternation of \"a\" or \"b\".)"))
(defclass lookahead (regex)
((regex :initarg :regex
:accessor regex
:documentation "The REGEX object we're checking.")
(positivep :initarg :positivep
:reader positivep
:documentation "Whether this assertion is positive."))
(:documentation "LOOKAHEAD objects represent look-ahead assertions."))
(defclass lookbehind (regex)
((regex :initarg :regex
:accessor regex
:documentation "The REGEX object we're checking.")
(positivep :initarg :positivep
:reader positivep
:documentation "Whether this assertion is positive.")
(len :initarg :len
:accessor len
:type fixnum
:documentation "The \(fixed) length of the enclosed regex."))
(:documentation "LOOKBEHIND objects represent look-behind assertions."))
(defclass repetition (regex)
((regex :initarg :regex
:accessor regex
:documentation "The REGEX that's repeated.")
(greedyp :initarg :greedyp
:reader greedyp
:documentation "Whether the repetition is greedy.")
(minimum :initarg :minimum
:accessor minimum
:type fixnum
:documentation "The minimal number of repetitions.")
(maximum :initarg :maximum
:accessor maximum
:documentation "The maximal number of repetitions.
Can be NIL for unbounded.")
(min-len :initarg :min-len
:reader min-len
:documentation "The minimal length of the enclosed regex.")
(len :initarg :len
:reader len
:documentation "The length of the enclosed regex. NIL if
unknown.")
(min-rest :initform 0
:accessor min-rest
:type fixnum
:documentation "The minimal number of characters which
must appear after this repetition.")
(contains-register-p :initarg :contains-register-p
:reader contains-register-p
:documentation "Whether the regex contains a
register."))
(:documentation "REPETITION objects represent repetitions of regexes."))
(defmethod print-object ((repetition repetition) stream)
(print-unreadable-object (repetition stream :type t :identity t)
(princ (regex repetition) stream)))
(defclass register (regex)
((regex :initarg :regex
:accessor regex
:documentation "The inner regex.")
(num :initarg :num
:reader num
:type fixnum
:documentation "The number of this register, starting from 0.
This is the index into *REGS-START* and *REGS-END*.")
(name :initarg :name
:reader name
:documentation "Name of this register or NIL."))
(:documentation "REGISTER objects represent register groups."))
(defmethod print-object ((register register) stream)
(print-unreadable-object (register stream :type t :identity t)
(princ (regex register) stream)))
(defclass standalone (regex)
((regex :initarg :regex
:accessor regex
:documentation "The inner regex."))
(:documentation "A standalone regular expression."))
(defclass back-reference (regex)
((num :initarg :num
:accessor num
:type fixnum
:documentation "The number of the register this
reference refers to.")
(name :initarg :name
:accessor name
:documentation "The name of the register this
reference refers to or NIL.")
(case-insensitive-p :initarg :case-insensitive-p
:reader case-insensitive-p
:documentation "Whether we check
case-insensitively."))
(:documentation "BACK-REFERENCE objects represent backreferences."))
(defclass char-class (regex)
((test-function :initarg :test-function
:reader test-function
:type (or function symbol nil)
:documentation "A unary function \(accepting a
character) which stands in for the character class and does the work
of checking whether a character belongs to the class."))
(:documentation "CHAR-CLASS objects represent character classes."))
(defclass str (regex)
((str :initarg :str
:accessor str
:type string
:documentation "The actual string.")
(len :initform 0
:accessor len
:type fixnum
:documentation "The length of the string.")
(case-insensitive-p :initarg :case-insensitive-p
:reader case-insensitive-p
:documentation "If we match case-insensitively.")
(offset :initform nil
:accessor offset
:documentation "Offset from the left of the whole
parse tree. The first regex has offset 0. NIL if unknown, i.e. behind
a variable-length regex.")
(skip :initform nil
:initarg :skip
:accessor skip
:documentation "If we can avoid testing for this
string because the SCAN function has done this already.")
(start-of-end-string-p :initform nil
:accessor start-of-end-string-p
:documentation "If this is the unique
STR which starts END-STRING (a slot of MATCHER)."))
(:documentation "STR objects represent string."))
(defmethod print-object ((str str) stream)
(print-unreadable-object (str stream :type t :identity t)
(princ (str str) stream)))
(defclass anchor (regex)
((startp :initarg :startp
:reader startp
:documentation "Whether this is a \"start anchor\".")
(multi-line-p :initarg :multi-line-p
:initform nil
:reader multi-line-p
:documentation "Whether we're in multi-line mode,
i.e. whether each #\\Newline is surrounded by anchors.")
(no-newline-p :initarg :no-newline-p
:initform nil
:reader no-newline-p
:documentation "Whether we ignore #\\Newline at the end."))
(:documentation "ANCHOR objects represent anchors like \"^\" or \"$\"."))
(defclass everything (regex)
((single-line-p :initarg :single-line-p
:reader single-line-p
:documentation "Whether we're in single-line mode,
i.e. whether we also match #\\Newline."))
(:documentation "EVERYTHING objects represent regexes matching
\"everything\", i.e. dots."))
(defclass word-boundary (regex)
((negatedp :initarg :negatedp
:reader negatedp
:documentation "Whether we mean the opposite,
i.e. no word-boundary."))
(:documentation "WORD-BOUNDARY objects represent word-boundary assertions."))
(defclass branch (regex)
((test :initarg :test
:accessor test
:documentation "The test of this branch, one of
LOOKAHEAD, LOOKBEHIND, or a number.")
(then-regex :initarg :then-regex
:accessor then-regex
:documentation "The regex that's to be matched if the
test succeeds.")
(else-regex :initarg :else-regex
:initform (make-instance 'void)
:accessor else-regex
:documentation "The regex that's to be matched if the
test fails."))
(:documentation "BRANCH objects represent Perl's conditional regular
expressions."))
(defclass filter (regex)
((fn :initarg :fn
:accessor fn
:type (or function symbol)
:documentation "The user-defined function.")
(len :initarg :len
:reader len
:documentation "The fixed length of this filter or NIL."))
(:documentation "FILTER objects represent arbitrary functions
defined by the user."))
(defclass void (regex)
()
(:documentation "VOID objects represent empty regular expressions."))
(defmethod initialize-instance :after ((str str) &rest init-args)
(declare #.*standard-optimize-settings*)
(declare (ignore init-args))
"Automatically computes the length of a STR after initialization."
(let ((str-slot (slot-value str 'str)))
(unless (typep str-slot
#-:lispworks 'simple-string
#+:lispworks 'lw:simple-text-string)
(setf (slot-value str 'str)
(coerce str-slot
#-:lispworks 'simple-string
#+:lispworks 'lw:simple-text-string))))
(setf (len str) (length (str str))))

View file

@ -0,0 +1,833 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/repetition-closures.lisp,v 1.34 2009/09/17 19:17:31 edi Exp $
;;; This is actually a part of closures.lisp which we put into a
;;; separate file because it is rather complex. We only deal with
;;; REPETITIONs here. Note that this part of the code contains some
;;; rather crazy micro-optimizations which were introduced to be as
;;; competitive with Perl as possible in tight loops.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defmacro incf-after (place &optional (delta 1) &environment env)
"Utility macro inspired by C's \"place++\", i.e. first return the
value of PLACE and afterwards increment it by DELTA."
(with-unique-names (%temp)
(multiple-value-bind (vars vals store-vars writer-form reader-form)
(get-setf-expansion place env)
`(let* (,@(mapcar #'list vars vals)
(,%temp ,reader-form)
(,(car store-vars) (+ ,%temp ,delta)))
,writer-form
,%temp))))
;; code for greedy repetitions with minimum zero
(defmacro greedy-constant-length-closure (check-curr-pos)
"This is the template for simple greedy repetitions (where simple
means that the minimum number of repetitions is zero, that the inner
regex to be checked is of fixed length LEN, and that it doesn't
contain registers, i.e. there's no need for backtracking).
CHECK-CURR-POS is a form which checks whether the inner regex of the
repetition matches at CURR-POS."
`(if maximum
(lambda (start-pos)
(declare (fixnum start-pos maximum))
;; because we know LEN we know in advance where to stop at the
;; latest; we also take into consideration MIN-REST, i.e. the
;; minimal length of the part behind the repetition
(let ((target-end-pos (min (1+ (- *end-pos* len min-rest))
;; don't go further than MAXIMUM
;; repetitions, of course
(+ start-pos
(the fixnum (* len maximum)))))
(curr-pos start-pos))
(declare (fixnum target-end-pos curr-pos))
(block greedy-constant-length-matcher
;; we use an ugly TAGBODY construct because this might be a
;; tight loop and this version is a bit faster than our LOOP
;; version (at least in CMUCL)
(tagbody
forward-loop
;; first go forward as far as possible, i.e. while
;; the inner regex matches
(when (>= curr-pos target-end-pos)
(go backward-loop))
(when ,check-curr-pos
(incf curr-pos len)
(go forward-loop))
backward-loop
;; now go back LEN steps each until we're able to match
;; the rest of the regex
(when (< curr-pos start-pos)
(return-from greedy-constant-length-matcher nil))
(let ((result (funcall next-fn curr-pos)))
(when result
(return-from greedy-constant-length-matcher result)))
(decf curr-pos len)
(go backward-loop)))))
;; basically the same code; it's just a bit easier because we're
;; not bounded by MAXIMUM
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((target-end-pos (1+ (- *end-pos* len min-rest)))
(curr-pos start-pos))
(declare (fixnum target-end-pos curr-pos))
(block greedy-constant-length-matcher
(tagbody
forward-loop
(when (>= curr-pos target-end-pos)
(go backward-loop))
(when ,check-curr-pos
(incf curr-pos len)
(go forward-loop))
backward-loop
(when (< curr-pos start-pos)
(return-from greedy-constant-length-matcher nil))
(let ((result (funcall next-fn curr-pos)))
(when result
(return-from greedy-constant-length-matcher result)))
(decf curr-pos len)
(go backward-loop)))))))
(defun create-greedy-everything-matcher (maximum min-rest next-fn)
"Creates a closure which just matches as far ahead as possible,
i.e. a closure for a dot in single-line mode."
(declare #.*standard-optimize-settings*)
(declare (fixnum min-rest) (function next-fn))
(if maximum
(lambda (start-pos)
(declare (fixnum start-pos maximum))
;; because we know LEN we know in advance where to stop at the
;; latest; we also take into consideration MIN-REST, i.e. the
;; minimal length of the part behind the repetition
(let ((target-end-pos (min (+ start-pos maximum)
(- *end-pos* min-rest))))
(declare (fixnum target-end-pos))
;; start from the highest possible position and go backward
;; until we're able to match the rest of the regex
(loop for curr-pos of-type fixnum from target-end-pos downto start-pos
thereis (funcall next-fn curr-pos))))
;; basically the same code; it's just a bit easier because we're
;; not bounded by MAXIMUM
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((target-end-pos (- *end-pos* min-rest)))
(declare (fixnum target-end-pos))
(loop for curr-pos of-type fixnum from target-end-pos downto start-pos
thereis (funcall next-fn curr-pos))))))
(defgeneric create-greedy-constant-length-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION is greedy and the minimal number of
repetitions is zero. It is furthermore assumed that the inner regex
of REPETITION is of fixed length and doesn't contain registers."))
(defmethod create-greedy-constant-length-matcher ((repetition repetition)
next-fn)
(declare #.*standard-optimize-settings*)
(let ((len (len repetition))
(maximum (maximum repetition))
(regex (regex repetition))
(min-rest (min-rest repetition)))
(declare (fixnum len min-rest)
(function next-fn))
(cond ((zerop len)
;; inner regex has zero-length, so we can discard it
;; completely
next-fn)
(t
;; now first try to optimize for a couple of common cases
(typecase regex
(str
(let ((str (str regex)))
(if (= 1 len)
;; a single character
(let ((chr (schar str 0)))
(if (case-insensitive-p regex)
(greedy-constant-length-closure
(char-equal chr (schar *string* curr-pos)))
(greedy-constant-length-closure
(char= chr (schar *string* curr-pos)))))
;; a string
(if (case-insensitive-p regex)
(greedy-constant-length-closure
(*string*-equal str curr-pos (+ curr-pos len) 0 len))
(greedy-constant-length-closure
(*string*= str curr-pos (+ curr-pos len) 0 len))))))
(char-class
;; a character class
(insert-char-class-tester (regex (schar *string* curr-pos))
(greedy-constant-length-closure
(char-class-test))))
(everything
;; an EVERYTHING object, i.e. a dot
(if (single-line-p regex)
(create-greedy-everything-matcher maximum min-rest next-fn)
(greedy-constant-length-closure
(char/= #\Newline (schar *string* curr-pos)))))
(t
;; the general case - we build an inner matcher which
;; just checks for immediate success, i.e. NEXT-FN is
;; #'IDENTITY
(let ((inner-matcher (create-matcher-aux regex #'identity)))
(declare (function inner-matcher))
(greedy-constant-length-closure
(funcall inner-matcher curr-pos)))))))))
(defgeneric create-greedy-no-zero-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION is greedy and the minimal number of
repetitions is zero. It is furthermore assumed that the inner regex
of REPETITION can never match a zero-length string \(or instead the
maximal number of repetitions is 1)."))
(defmethod create-greedy-no-zero-matcher ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(let ((maximum (maximum repetition))
;; REPEAT-MATCHER is part of the closure's environment but it
;; can only be defined after GREEDY-AUX is defined
repeat-matcher)
(declare (function next-fn))
(cond
((eql maximum 1)
;; this is essentially like the next case but with a known
;; MAXIMUM of 1 we can get away without a counter; note that
;; we always arrive here if CONVERT optimizes <regex>* to
;; (?:<regex'>*<regex>)?
(setq repeat-matcher
(create-matcher-aux (regex repetition) next-fn))
(lambda (start-pos)
(declare (function repeat-matcher))
(or (funcall repeat-matcher start-pos)
(funcall next-fn start-pos))))
(maximum
;; we make a reservation for our slot in *REPEAT-COUNTERS*
;; because we need to keep track whether we've reached MAXIMUM
;; repetitions
(let ((rep-num (incf-after *rep-num*)))
(flet ((greedy-aux (start-pos)
(declare (fixnum start-pos maximum rep-num)
(function repeat-matcher))
;; the actual matcher which first tries to match the
;; inner regex of REPETITION (if we haven't done so
;; too often) and on failure calls NEXT-FN
(or (and (< (aref *repeat-counters* rep-num) maximum)
(incf (aref *repeat-counters* rep-num))
;; note that REPEAT-MATCHER will call
;; GREEDY-AUX again recursively
(prog1
(funcall repeat-matcher start-pos)
(decf (aref *repeat-counters* rep-num))))
(funcall next-fn start-pos))))
;; create a closure to match the inner regex and to
;; implement backtracking via GREEDY-AUX
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'greedy-aux))
;; the closure we return is just a thin wrapper around
;; GREEDY-AUX to initialize the repetition counter
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (aref *repeat-counters* rep-num) 0)
(greedy-aux start-pos)))))
(t
;; easier code because we're not bounded by MAXIMUM, but
;; basically the same
(flet ((greedy-aux (start-pos)
(declare (fixnum start-pos)
(function repeat-matcher))
(or (funcall repeat-matcher start-pos)
(funcall next-fn start-pos))))
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'greedy-aux))
#'greedy-aux)))))
(defgeneric create-greedy-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION is greedy and the minimal number of
repetitions is zero."))
(defmethod create-greedy-matcher ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(let ((maximum (maximum repetition))
;; we make a reservation for our slot in *LAST-POS-STORES* because
;; we have to watch out for endless loops as the inner regex might
;; match zero-length strings
(zero-length-num (incf-after *zero-length-num*))
;; REPEAT-MATCHER is part of the closure's environment but it
;; can only be defined after GREEDY-AUX is defined
repeat-matcher)
(declare (fixnum zero-length-num)
(function next-fn))
(cond
(maximum
;; we make a reservation for our slot in *REPEAT-COUNTERS*
;; because we need to keep track whether we've reached MAXIMUM
;; repetitions
(let ((rep-num (incf-after *rep-num*)))
(flet ((greedy-aux (start-pos)
;; the actual matcher which first tries to match the
;; inner regex of REPETITION (if we haven't done so
;; too often) and on failure calls NEXT-FN
(declare (fixnum start-pos maximum rep-num)
(function repeat-matcher))
(let ((old-last-pos
(svref *last-pos-stores* zero-length-num)))
(when (and old-last-pos
(= (the fixnum old-last-pos) start-pos))
;; stop immediately if we've been here before,
;; i.e. if the last attempt matched a zero-length
;; string
(return-from greedy-aux (funcall next-fn start-pos)))
;; otherwise remember this position for the next
;; repetition
(setf (svref *last-pos-stores* zero-length-num) start-pos)
(or (and (< (aref *repeat-counters* rep-num) maximum)
(incf (aref *repeat-counters* rep-num))
;; note that REPEAT-MATCHER will call
;; GREEDY-AUX again recursively
(prog1
(funcall repeat-matcher start-pos)
(decf (aref *repeat-counters* rep-num))
(setf (svref *last-pos-stores* zero-length-num)
old-last-pos)))
(funcall next-fn start-pos)))))
;; create a closure to match the inner regex and to
;; implement backtracking via GREEDY-AUX
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'greedy-aux))
;; the closure we return is just a thin wrapper around
;; GREEDY-AUX to initialize the repetition counter and our
;; slot in *LAST-POS-STORES*
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (aref *repeat-counters* rep-num) 0
(svref *last-pos-stores* zero-length-num) nil)
(greedy-aux start-pos)))))
(t
;; easier code because we're not bounded by MAXIMUM, but
;; basically the same
(flet ((greedy-aux (start-pos)
(declare (fixnum start-pos)
(function repeat-matcher))
(let ((old-last-pos
(svref *last-pos-stores* zero-length-num)))
(when (and old-last-pos
(= (the fixnum old-last-pos) start-pos))
(return-from greedy-aux (funcall next-fn start-pos)))
(setf (svref *last-pos-stores* zero-length-num) start-pos)
(or (prog1
(funcall repeat-matcher start-pos)
(setf (svref *last-pos-stores* zero-length-num) old-last-pos))
(funcall next-fn start-pos)))))
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'greedy-aux))
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (svref *last-pos-stores* zero-length-num) nil)
(greedy-aux start-pos)))))))
;; code for non-greedy repetitions with minimum zero
(defmacro non-greedy-constant-length-closure (check-curr-pos)
"This is the template for simple non-greedy repetitions \(where
simple means that the minimum number of repetitions is zero, that the
inner regex to be checked is of fixed length LEN, and that it doesn't
contain registers, i.e. there's no need for backtracking).
CHECK-CURR-POS is a form which checks whether the inner regex of the
repetition matches at CURR-POS."
`(if maximum
(lambda (start-pos)
(declare (fixnum start-pos maximum))
;; because we know LEN we know in advance where to stop at the
;; latest; we also take into consideration MIN-REST, i.e. the
;; minimal length of the part behind the repetition
(let ((target-end-pos (min (1+ (- *end-pos* len min-rest))
(+ start-pos
(the fixnum (* len maximum))))))
;; move forward by LEN and always try NEXT-FN first, then
;; CHECK-CUR-POS
(loop for curr-pos of-type fixnum from start-pos
below target-end-pos
by len
thereis (funcall next-fn curr-pos)
while ,check-curr-pos
finally (return (funcall next-fn curr-pos)))))
;; basically the same code; it's just a bit easier because we're
;; not bounded by MAXIMUM
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((target-end-pos (1+ (- *end-pos* len min-rest))))
(loop for curr-pos of-type fixnum from start-pos
below target-end-pos
by len
thereis (funcall next-fn curr-pos)
while ,check-curr-pos
finally (return (funcall next-fn curr-pos)))))))
(defgeneric create-non-greedy-constant-length-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION is non-greedy and the minimal number of
repetitions is zero. It is furthermore assumed that the inner regex
of REPETITION is of fixed length and doesn't contain registers."))
(defmethod create-non-greedy-constant-length-matcher ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(let ((len (len repetition))
(maximum (maximum repetition))
(regex (regex repetition))
(min-rest (min-rest repetition)))
(declare (fixnum len min-rest)
(function next-fn))
(cond ((zerop len)
;; inner regex has zero-length, so we can discard it
;; completely
next-fn)
(t
;; now first try to optimize for a couple of common cases
(typecase regex
(str
(let ((str (str regex)))
(if (= 1 len)
;; a single character
(let ((chr (schar str 0)))
(if (case-insensitive-p regex)
(non-greedy-constant-length-closure
(char-equal chr (schar *string* curr-pos)))
(non-greedy-constant-length-closure
(char= chr (schar *string* curr-pos)))))
;; a string
(if (case-insensitive-p regex)
(non-greedy-constant-length-closure
(*string*-equal str curr-pos (+ curr-pos len) 0 len))
(non-greedy-constant-length-closure
(*string*= str curr-pos (+ curr-pos len) 0 len))))))
(char-class
;; a character class
(insert-char-class-tester (regex (schar *string* curr-pos))
(non-greedy-constant-length-closure
(char-class-test))))
(everything
(if (single-line-p regex)
;; a dot which really can match everything; we rely
;; on the compiler to optimize this away
(non-greedy-constant-length-closure
t)
;; a dot which has to watch out for #\Newline
(non-greedy-constant-length-closure
(char/= #\Newline (schar *string* curr-pos)))))
(t
;; the general case - we build an inner matcher which
;; just checks for immediate success, i.e. NEXT-FN is
;; #'IDENTITY
(let ((inner-matcher (create-matcher-aux regex #'identity)))
(declare (function inner-matcher))
(non-greedy-constant-length-closure
(funcall inner-matcher curr-pos)))))))))
(defgeneric create-non-greedy-no-zero-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION is non-greedy and the minimal number of
repetitions is zero. It is furthermore assumed that the inner regex
of REPETITION can never match a zero-length string \(or instead the
maximal number of repetitions is 1)."))
(defmethod create-non-greedy-no-zero-matcher ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(let ((maximum (maximum repetition))
;; REPEAT-MATCHER is part of the closure's environment but it
;; can only be defined after NON-GREEDY-AUX is defined
repeat-matcher)
(declare (function next-fn))
(cond
((eql maximum 1)
;; this is essentially like the next case but with a known
;; MAXIMUM of 1 we can get away without a counter
(setq repeat-matcher
(create-matcher-aux (regex repetition) next-fn))
(lambda (start-pos)
(declare (function repeat-matcher))
(or (funcall next-fn start-pos)
(funcall repeat-matcher start-pos))))
(maximum
;; we make a reservation for our slot in *REPEAT-COUNTERS*
;; because we need to keep track whether we've reached MAXIMUM
;; repetitions
(let ((rep-num (incf-after *rep-num*)))
(flet ((non-greedy-aux (start-pos)
;; the actual matcher which first calls NEXT-FN and
;; on failure tries to match the inner regex of
;; REPETITION (if we haven't done so too often)
(declare (fixnum start-pos maximum rep-num)
(function repeat-matcher))
(or (funcall next-fn start-pos)
(and (< (aref *repeat-counters* rep-num) maximum)
(incf (aref *repeat-counters* rep-num))
;; note that REPEAT-MATCHER will call
;; NON-GREEDY-AUX again recursively
(prog1
(funcall repeat-matcher start-pos)
(decf (aref *repeat-counters* rep-num)))))))
;; create a closure to match the inner regex and to
;; implement backtracking via NON-GREEDY-AUX
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'non-greedy-aux))
;; the closure we return is just a thin wrapper around
;; NON-GREEDY-AUX to initialize the repetition counter
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (aref *repeat-counters* rep-num) 0)
(non-greedy-aux start-pos)))))
(t
;; easier code because we're not bounded by MAXIMUM, but
;; basically the same
(flet ((non-greedy-aux (start-pos)
(declare (fixnum start-pos)
(function repeat-matcher))
(or (funcall next-fn start-pos)
(funcall repeat-matcher start-pos))))
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'non-greedy-aux))
#'non-greedy-aux)))))
(defgeneric create-non-greedy-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION is non-greedy and the minimal number of
repetitions is zero."))
(defmethod create-non-greedy-matcher ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
;; we make a reservation for our slot in *LAST-POS-STORES* because
;; we have to watch out for endless loops as the inner regex might
;; match zero-length strings
(let ((zero-length-num (incf-after *zero-length-num*))
(maximum (maximum repetition))
;; REPEAT-MATCHER is part of the closure's environment but it
;; can only be defined after NON-GREEDY-AUX is defined
repeat-matcher)
(declare (fixnum zero-length-num)
(function next-fn))
(cond
(maximum
;; we make a reservation for our slot in *REPEAT-COUNTERS*
;; because we need to keep track whether we've reached MAXIMUM
;; repetitions
(let ((rep-num (incf-after *rep-num*)))
(flet ((non-greedy-aux (start-pos)
;; the actual matcher which first calls NEXT-FN and
;; on failure tries to match the inner regex of
;; REPETITION (if we haven't done so too often)
(declare (fixnum start-pos maximum rep-num)
(function repeat-matcher))
(let ((old-last-pos
(svref *last-pos-stores* zero-length-num)))
(when (and old-last-pos
(= (the fixnum old-last-pos) start-pos))
;; stop immediately if we've been here before,
;; i.e. if the last attempt matched a zero-length
;; string
(return-from non-greedy-aux (funcall next-fn start-pos)))
;; otherwise remember this position for the next
;; repetition
(setf (svref *last-pos-stores* zero-length-num) start-pos)
(or (funcall next-fn start-pos)
(and (< (aref *repeat-counters* rep-num) maximum)
(incf (aref *repeat-counters* rep-num))
;; note that REPEAT-MATCHER will call
;; NON-GREEDY-AUX again recursively
(prog1
(funcall repeat-matcher start-pos)
(decf (aref *repeat-counters* rep-num))
(setf (svref *last-pos-stores* zero-length-num)
old-last-pos)))))))
;; create a closure to match the inner regex and to
;; implement backtracking via NON-GREEDY-AUX
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'non-greedy-aux))
;; the closure we return is just a thin wrapper around
;; NON-GREEDY-AUX to initialize the repetition counter and our
;; slot in *LAST-POS-STORES*
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (aref *repeat-counters* rep-num) 0
(svref *last-pos-stores* zero-length-num) nil)
(non-greedy-aux start-pos)))))
(t
;; easier code because we're not bounded by MAXIMUM, but
;; basically the same
(flet ((non-greedy-aux (start-pos)
(declare (fixnum start-pos)
(function repeat-matcher))
(let ((old-last-pos
(svref *last-pos-stores* zero-length-num)))
(when (and old-last-pos
(= (the fixnum old-last-pos) start-pos))
(return-from non-greedy-aux (funcall next-fn start-pos)))
(setf (svref *last-pos-stores* zero-length-num) start-pos)
(or (funcall next-fn start-pos)
(prog1
(funcall repeat-matcher start-pos)
(setf (svref *last-pos-stores* zero-length-num)
old-last-pos))))))
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'non-greedy-aux))
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (svref *last-pos-stores* zero-length-num) nil)
(non-greedy-aux start-pos)))))))
;; code for constant repetitions, i.e. those with a fixed number of repetitions
(defmacro constant-repetition-constant-length-closure (check-curr-pos)
"This is the template for simple constant repetitions (where simple
means that the inner regex to be checked is of fixed length LEN, and
that it doesn't contain registers, i.e. there's no need for
backtracking) and where constant means that MINIMUM is equal to
MAXIMUM. CHECK-CURR-POS is a form which checks whether the inner
regex of the repetition matches at CURR-POS."
`(lambda (start-pos)
(declare (fixnum start-pos))
(let ((target-end-pos (+ start-pos
(the fixnum (* len repetitions)))))
(declare (fixnum target-end-pos))
;; first check if we won't go beyond the end of the string
(and (>= *end-pos* target-end-pos)
;; then loop through all repetitions step by step
(loop for curr-pos of-type fixnum from start-pos
below target-end-pos
by len
always ,check-curr-pos)
;; finally call NEXT-FN if we made it that far
(funcall next-fn target-end-pos)))))
(defgeneric create-constant-repetition-constant-length-matcher
(repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION has a constant number of repetitions.
It is furthermore assumed that the inner regex of REPETITION is of
fixed length and doesn't contain registers."))
(defmethod create-constant-repetition-constant-length-matcher
((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(let ((len (len repetition))
(repetitions (minimum repetition))
(regex (regex repetition)))
(declare (fixnum len repetitions)
(function next-fn))
(if (zerop len)
;; if the length is zero it suffices to try once
(create-matcher-aux regex next-fn)
;; otherwise try to optimize for a couple of common cases
(typecase regex
(str
(let ((str (str regex)))
(if (= 1 len)
;; a single character
(let ((chr (schar str 0)))
(if (case-insensitive-p regex)
(constant-repetition-constant-length-closure
(and (char-equal chr (schar *string* curr-pos))
(1+ curr-pos)))
(constant-repetition-constant-length-closure
(and (char= chr (schar *string* curr-pos))
(1+ curr-pos)))))
;; a string
(if (case-insensitive-p regex)
(constant-repetition-constant-length-closure
(let ((next-pos (+ curr-pos len)))
(declare (fixnum next-pos))
(and (*string*-equal str curr-pos next-pos 0 len)
next-pos)))
(constant-repetition-constant-length-closure
(let ((next-pos (+ curr-pos len)))
(declare (fixnum next-pos))
(and (*string*= str curr-pos next-pos 0 len)
next-pos)))))))
(char-class
;; a character class
(insert-char-class-tester (regex (schar *string* curr-pos))
(constant-repetition-constant-length-closure
(and (char-class-test)
(1+ curr-pos)))))
(everything
(if (single-line-p regex)
;; a dot which really matches everything - we just have to
;; advance the index into *STRING* accordingly and check
;; if we didn't go past the end
(lambda (start-pos)
(declare (fixnum start-pos))
(let ((next-pos (+ start-pos repetitions)))
(declare (fixnum next-pos))
(and (<= next-pos *end-pos*)
(funcall next-fn next-pos))))
;; a dot which is not in single-line-mode - make sure we
;; don't match #\Newline
(constant-repetition-constant-length-closure
(and (char/= #\Newline (schar *string* curr-pos))
(1+ curr-pos)))))
(t
;; the general case - we build an inner matcher which just
;; checks for immediate success, i.e. NEXT-FN is #'IDENTITY
(let ((inner-matcher (create-matcher-aux regex #'identity)))
(declare (function inner-matcher))
(constant-repetition-constant-length-closure
(funcall inner-matcher curr-pos))))))))
(defgeneric create-constant-repetition-matcher (repetition next-fn)
(declare #.*standard-optimize-settings*)
(:documentation "Creates a closure which tries to match REPETITION.
It is assumed that REPETITION has a constant number of repetitions."))
(defmethod create-constant-repetition-matcher ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(let ((repetitions (minimum repetition))
;; we make a reservation for our slot in *REPEAT-COUNTERS*
;; because we need to keep track of the number of repetitions
(rep-num (incf-after *rep-num*))
;; REPEAT-MATCHER is part of the closure's environment but it
;; can only be defined after NON-GREEDY-AUX is defined
repeat-matcher)
(declare (fixnum repetitions rep-num)
(function next-fn))
(if (zerop (min-len repetition))
;; we make a reservation for our slot in *LAST-POS-STORES*
;; because we have to watch out for needless loops as the inner
;; regex might match zero-length strings
(let ((zero-length-num (incf-after *zero-length-num*)))
(declare (fixnum zero-length-num))
(flet ((constant-aux (start-pos)
;; the actual matcher which first calls NEXT-FN and
;; on failure tries to match the inner regex of
;; REPETITION (if we haven't done so too often)
(declare (fixnum start-pos)
(function repeat-matcher))
(let ((old-last-pos
(svref *last-pos-stores* zero-length-num)))
(when (and old-last-pos
(= (the fixnum old-last-pos) start-pos))
;; if we've been here before we matched a
;; zero-length string the last time, so we can
;; just carry on because we will definitely be
;; able to do this again often enough
(return-from constant-aux (funcall next-fn start-pos)))
;; otherwise remember this position for the next
;; repetition
(setf (svref *last-pos-stores* zero-length-num) start-pos)
(cond ((< (aref *repeat-counters* rep-num) repetitions)
;; not enough repetitions yet, try it again
(incf (aref *repeat-counters* rep-num))
;; note that REPEAT-MATCHER will call
;; CONSTANT-AUX again recursively
(prog1
(funcall repeat-matcher start-pos)
(decf (aref *repeat-counters* rep-num))
(setf (svref *last-pos-stores* zero-length-num)
old-last-pos)))
(t
;; we're done - call NEXT-FN
(funcall next-fn start-pos))))))
;; create a closure to match the inner regex and to
;; implement backtracking via CONSTANT-AUX
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'constant-aux))
;; the closure we return is just a thin wrapper around
;; CONSTANT-AUX to initialize the repetition counter
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (aref *repeat-counters* rep-num) 0
(aref *last-pos-stores* zero-length-num) nil)
(constant-aux start-pos))))
;; easier code because we don't have to care about zero-length
;; matches but basically the same
(flet ((constant-aux (start-pos)
(declare (fixnum start-pos)
(function repeat-matcher))
(cond ((< (aref *repeat-counters* rep-num) repetitions)
(incf (aref *repeat-counters* rep-num))
(prog1
(funcall repeat-matcher start-pos)
(decf (aref *repeat-counters* rep-num))))
(t (funcall next-fn start-pos)))))
(setq repeat-matcher
(create-matcher-aux (regex repetition) #'constant-aux))
(lambda (start-pos)
(declare (fixnum start-pos))
(setf (aref *repeat-counters* rep-num) 0)
(constant-aux start-pos))))))
;; the actual CREATE-MATCHER-AUX method for REPETITION objects which
;; utilizes all the functions and macros defined above
(defmethod create-matcher-aux ((repetition repetition) next-fn)
(declare #.*standard-optimize-settings*)
(with-slots (minimum maximum len min-len greedyp contains-register-p)
repetition
(cond ((and maximum
(zerop maximum))
;; this should have been optimized away by CONVERT but just
;; in case...
(error "Got REPETITION with MAXIMUM 0 \(should not happen)"))
((and maximum
(= minimum maximum 1))
;; this should have been optimized away by CONVERT but just
;; in case...
(error "Got REPETITION with MAXIMUM 1 and MINIMUM 1 \(should not happen)"))
((and (eql minimum maximum)
len
(not contains-register-p))
(create-constant-repetition-constant-length-matcher repetition next-fn))
((eql minimum maximum)
(create-constant-repetition-matcher repetition next-fn))
((and greedyp
len
(not contains-register-p))
(create-greedy-constant-length-matcher repetition next-fn))
((and greedyp
(or (plusp min-len)
(eql maximum 1)))
(create-greedy-no-zero-matcher repetition next-fn))
(greedyp
(create-greedy-matcher repetition next-fn))
((and len
(plusp len)
(not contains-register-p))
(create-non-greedy-constant-length-matcher repetition next-fn))
((or (plusp min-len)
(eql maximum 1))
(create-non-greedy-no-zero-matcher repetition next-fn))
(t
(create-non-greedy-matcher repetition next-fn)))))

View file

@ -0,0 +1,506 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/scanner.lisp,v 1.36 2009/09/17 19:17:31 edi Exp $
;;; Here the scanner for the actual regex as well as utility scanners
;;; for the constant start and end strings are created.
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
(defmacro bmh-matcher-aux (&key case-insensitive-p)
"Auxiliary macro used by CREATE-BMH-MATCHER."
(let ((char-compare (if case-insensitive-p 'char-equal 'char=)))
`(lambda (start-pos)
(declare (fixnum start-pos))
(if (or (minusp start-pos)
(> (the fixnum (+ start-pos m)) *end-pos*))
nil
(loop named bmh-matcher
for k of-type fixnum = (+ start-pos m -1)
then (+ k (max 1 (aref skip (char-code (schar *string* k)))))
while (< k *end-pos*)
do (loop for j of-type fixnum downfrom (1- m)
for i of-type fixnum downfrom k
while (and (>= j 0)
(,char-compare (schar *string* i)
(schar pattern j)))
finally (if (minusp j)
(return-from bmh-matcher (1+ i)))))))))
(defun create-bmh-matcher (pattern case-insensitive-p)
"Returns a Boyer-Moore-Horspool matcher which searches the (special)
simple-string *STRING* for the first occurence of the substring
PATTERN. The search starts at the position START-POS within *STRING*
and stops before *END-POS* is reached. Depending on the second
argument the search is case-insensitive or not. If the special
variable *USE-BMH-MATCHERS* is NIL, use the standard SEARCH function
instead. \(BMH matchers are faster but need much more space.)"
(declare #.*standard-optimize-settings*)
;; see <http://www-igm.univ-mlv.fr/~lecroq/string/node18.html> for
;; details
(unless *use-bmh-matchers*
(let ((test (if case-insensitive-p #'char-equal #'char=)))
(return-from create-bmh-matcher
(lambda (start-pos)
(declare (fixnum start-pos))
(and (not (minusp start-pos))
(search pattern
*string*
:start2 start-pos
:end2 *end-pos*
:test test))))))
(let* ((m (length pattern))
(skip (make-array *regex-char-code-limit*
:element-type 'fixnum
:initial-element m)))
(declare (fixnum m))
(loop for k of-type fixnum below m
if case-insensitive-p
do (setf (aref skip (char-code (char-upcase (schar pattern k)))) (- m k 1)
(aref skip (char-code (char-downcase (schar pattern k)))) (- m k 1))
else
do (setf (aref skip (char-code (schar pattern k))) (- m k 1)))
(if case-insensitive-p
(bmh-matcher-aux :case-insensitive-p t)
(bmh-matcher-aux))))
(defmacro char-searcher-aux (&key case-insensitive-p)
"Auxiliary macro used by CREATE-CHAR-SEARCHER."
(let ((char-compare (if case-insensitive-p 'char-equal 'char=)))
`(lambda (start-pos)
(declare (fixnum start-pos))
(and (not (minusp start-pos))
(loop for i of-type fixnum from start-pos below *end-pos*
thereis (and (,char-compare (schar *string* i) chr) i))))))
(defun create-char-searcher (chr case-insensitive-p)
"Returns a function which searches the (special) simple-string
*STRING* for the first occurence of the character CHR. The search
starts at the position START-POS within *STRING* and stops before
*END-POS* is reached. Depending on the second argument the search is
case-insensitive or not."
(declare #.*standard-optimize-settings*)
(if case-insensitive-p
(char-searcher-aux :case-insensitive-p t)
(char-searcher-aux)))
(declaim (inline newline-skipper))
(defun newline-skipper (start-pos)
"Finds the next occurence of a character in *STRING* which is behind
a #\Newline."
(declare #.*standard-optimize-settings*)
(declare (fixnum start-pos))
;; we can start with (1- START-POS) without testing for (PLUSP
;; START-POS) because we know we'll never call NEWLINE-SKIPPER on
;; the first iteration
(loop for i of-type fixnum from (1- start-pos) below *end-pos*
thereis (and (char= (schar *string* i)
#\Newline)
(1+ i))))
(defmacro insert-advance-fn (advance-fn)
"Creates the actual closure returned by CREATE-SCANNER-AUX by
replacing '(ADVANCE-FN-DEFINITION) with a suitable definition for
ADVANCE-FN. This is a utility macro used by CREATE-SCANNER-AUX."
(subst
advance-fn '(advance-fn-definition)
'(lambda (string start end)
(block scan
;; initialize a couple of special variables used by the
;; matchers (see file specials.lisp)
(let* ((*string* string)
(*start-pos* start)
(*end-pos* end)
;; we will search forward for END-STRING if this value
;; isn't at least as big as POS (see ADVANCE-FN), so it
;; is safe to start to the left of *START-POS*; note
;; that this value will _never_ be decremented - this
;; is crucial to the scanning process
(*end-string-pos* (1- *start-pos*))
;; the next five will shadow the variables defined by
;; DEFPARAMETER; at this point, we don't know if we'll
;; actually use them, though
(*repeat-counters* *repeat-counters*)
(*last-pos-stores* *last-pos-stores*)
(*reg-starts* *reg-starts*)
(*regs-maybe-start* *regs-maybe-start*)
(*reg-ends* *reg-ends*)
;; we might be able to optimize the scanning process by
;; (virtually) shifting *START-POS* to the right
(scan-start-pos *start-pos*)
(starts-with-str (if start-string-test
(str starts-with)
nil))
;; we don't need to try further than MAX-END-POS
(max-end-pos (- *end-pos* min-len)))
(declare (fixnum scan-start-pos)
(function match-fn))
;; definition of ADVANCE-FN will be inserted here by macrology
(labels ((advance-fn-definition))
(declare (inline advance-fn))
(when (plusp rep-num)
;; we have at least one REPETITION which needs to count
;; the number of repetitions
(setq *repeat-counters* (make-array rep-num
:initial-element 0
:element-type 'fixnum)))
(when (plusp zero-length-num)
;; we have at least one REPETITION which needs to watch
;; out for zero-length repetitions
(setq *last-pos-stores* (make-array zero-length-num
:initial-element nil)))
(when (plusp reg-num)
;; we have registers in our regular expression
(setq *reg-starts* (make-array reg-num :initial-element nil)
*regs-maybe-start* (make-array reg-num :initial-element nil)
*reg-ends* (make-array reg-num :initial-element nil)))
(when end-anchored-p
;; the regular expression has a constant end string which
;; is anchored at the very end of the target string
;; (perhaps modulo a #\Newline)
(let ((end-test-pos (- *end-pos* (the fixnum end-string-len))))
(declare (fixnum end-test-pos)
(function end-string-test))
(unless (setq *end-string-pos* (funcall end-string-test
end-test-pos))
(when (and (= 1 (the fixnum end-anchored-p))
(> *end-pos* scan-start-pos)
(char= #\Newline (schar *string* (1- *end-pos*))))
;; if we didn't find an end string candidate from
;; END-TEST-POS and if a #\Newline at the end is
;; allowed we try it again from one position to the
;; left
(setq *end-string-pos* (funcall end-string-test
(1- end-test-pos))))))
(unless (and *end-string-pos*
(<= *start-pos* *end-string-pos*))
;; no end string candidate found, so give up
(return-from scan nil))
(when end-string-offset
;; if the offset of the constant end string from the
;; left of the regular expression is known we can start
;; scanning further to the right; this is similar to
;; what we might do in ADVANCE-FN
(setq scan-start-pos (max scan-start-pos
(- (the fixnum *end-string-pos*)
(the fixnum end-string-offset))))))
(cond
(start-anchored-p
;; we're anchored at the start of the target string,
;; so no need to try again after first failure
(when (or (/= *start-pos* scan-start-pos)
(< max-end-pos *start-pos*))
;; if END-STRING-OFFSET has proven that we don't
;; need to bother to scan from *START-POS* or if the
;; minimal length of the regular expression is
;; longer than the target string we give up
(return-from scan nil))
(when starts-with-str
(locally
(declare (fixnum starts-with-len))
(cond ((and (case-insensitive-p starts-with)
(not (*string*-equal starts-with-str
*start-pos*
(+ *start-pos*
starts-with-len)
0 starts-with-len)))
;; the regular expression has a
;; case-insensitive constant start string
;; and we didn't find it
(return-from scan nil))
((and (not (case-insensitive-p starts-with))
(not (*string*= starts-with-str
*start-pos*
(+ *start-pos* starts-with-len)
0 starts-with-len)))
;; the regular expression has a
;; case-sensitive constant start string
;; and we didn't find it
(return-from scan nil))
(t nil))))
(when (and end-string-test
(not end-anchored-p))
;; the regular expression has a constant end string
;; which isn't anchored so we didn't check for it
;; already
(block end-string-loop
;; we temporarily use *END-STRING-POS* as our
;; starting position to look for end string
;; candidates
(setq *end-string-pos* *start-pos*)
(loop
(unless (setq *end-string-pos*
(funcall (the function end-string-test)
*end-string-pos*))
;; no end string candidate found, so give up
(return-from scan nil))
(unless end-string-offset
;; end string doesn't have an offset so we
;; can start scanning now
(return-from end-string-loop))
(let ((maybe-start-pos (- (the fixnum *end-string-pos*)
(the fixnum end-string-offset))))
(cond ((= maybe-start-pos *start-pos*)
;; offset of end string into regular
;; expression matches start anchor -
;; fine...
(return-from end-string-loop))
((and (< maybe-start-pos *start-pos*)
(< (+ *end-string-pos* end-string-len) *end-pos*))
;; no match but maybe we find another
;; one to the right - try again
(incf *end-string-pos*))
(t
;; otherwise give up
(return-from scan nil)))))))
;; if we got here we scan exactly once
(let ((next-pos (funcall match-fn *start-pos*)))
(when next-pos
(values (if next-pos *start-pos* nil)
next-pos
*reg-starts*
*reg-ends*))))
(t
(loop for pos = (if starts-with-everything
;; don't jump to the next
;; #\Newline on the first
;; iteration
scan-start-pos
(advance-fn scan-start-pos))
then (advance-fn pos)
;; give up if the regular expression can't fit
;; into the rest of the target string
while (and pos
(<= (the fixnum pos) max-end-pos))
do (let ((next-pos (funcall match-fn pos)))
(when next-pos
(return-from scan (values pos
next-pos
*reg-starts*
*reg-ends*)))
;; not yet found, increment POS
#-cormanlisp (incf (the fixnum pos))
#+cormanlisp (incf pos)))))))))
:test #'equalp))
(defun create-scanner-aux (match-fn
min-len
start-anchored-p
starts-with
start-string-test
end-anchored-p
end-string-test
end-string-len
end-string-offset
rep-num
zero-length-num
reg-num)
"Auxiliary function to create and return a scanner \(which is
actually a closure). Used by CREATE-SCANNER."
(declare #.*standard-optimize-settings*)
(declare (fixnum min-len zero-length-num rep-num reg-num))
(let ((starts-with-len (if (typep starts-with 'str)
(len starts-with)))
(starts-with-everything (typep starts-with 'everything)))
(cond
;; this COND statement dispatches on the different versions we
;; have for ADVANCE-FN and creates different closures for each;
;; note that you see only the bodies of ADVANCE-FN below - the
;; actual scanner is defined in INSERT-ADVANCE-FN above; (we
;; could have done this with closures instead of macrology but
;; would have consed a lot more)
((and start-string-test end-string-test end-string-offset)
;; we know that the regular expression has constant start and
;; end strings and we know the end string's offset (from the
;; left)
(insert-advance-fn
(advance-fn (pos)
(declare (fixnum end-string-offset starts-with-len)
(function start-string-test end-string-test))
(loop
(unless (setq pos (funcall start-string-test pos))
;; give up completely if we can't find a start string
;; candidate
(return-from scan nil))
(locally
;; from here we know that POS is a FIXNUM
(declare (fixnum pos))
(when (= pos (- (the fixnum *end-string-pos*) end-string-offset))
;; if we already found an end string candidate the
;; position of which matches the start string
;; candidate we're done
(return-from advance-fn pos))
(let ((try-pos (+ pos starts-with-len)))
;; otherwise try (again) to find an end string
;; candidate which starts behind the start string
;; candidate
(loop
(unless (setq *end-string-pos*
(funcall end-string-test try-pos))
;; no end string candidate found, so give up
(return-from scan nil))
;; NEW-POS is where we should start scanning
;; according to the end string candidate
(let ((new-pos (- (the fixnum *end-string-pos*)
end-string-offset)))
(declare (fixnum new-pos *end-string-pos*))
(cond ((= new-pos pos)
;; if POS and NEW-POS are equal then the
;; two candidates agree so we're fine
(return-from advance-fn pos))
((> new-pos pos)
;; if NEW-POS is further to the right we
;; advance POS and try again, i.e. we go
;; back to the start of the outer LOOP
(setq pos new-pos)
;; this means "return from inner LOOP"
(return))
(t
;; otherwise NEW-POS is smaller than POS,
;; so we have to redo the inner LOOP to
;; find another end string candidate
;; further to the right
(setq try-pos (1+ *end-string-pos*))))))))))))
((and starts-with-everything end-string-test end-string-offset)
;; we know that the regular expression starts with ".*" (which
;; is not in single-line-mode, see CREATE-SCANNER-AUX) and ends
;; with a constant end string and we know the end string's
;; offset (from the left)
(insert-advance-fn
(advance-fn (pos)
(declare (fixnum end-string-offset)
(function end-string-test))
(loop
(unless (setq pos (newline-skipper pos))
;; if we can't find a #\Newline we give up immediately
(return-from scan nil))
(locally
;; from here we know that POS is a FIXNUM
(declare (fixnum pos))
(when (= pos (- (the fixnum *end-string-pos*) end-string-offset))
;; if we already found an end string candidate the
;; position of which matches the place behind the
;; #\Newline we're done
(return-from advance-fn pos))
(let ((try-pos pos))
;; otherwise try (again) to find an end string
;; candidate which starts behind the #\Newline
(loop
(unless (setq *end-string-pos*
(funcall end-string-test try-pos))
;; no end string candidate found, so we give up
(return-from scan nil))
;; NEW-POS is where we should start scanning
;; according to the end string candidate
(let ((new-pos (- (the fixnum *end-string-pos*)
end-string-offset)))
(declare (fixnum new-pos *end-string-pos*))
(cond ((= new-pos pos)
;; if POS and NEW-POS are equal then the
;; the end string candidate agrees with
;; the #\Newline so we're fine
(return-from advance-fn pos))
((> new-pos pos)
;; if NEW-POS is further to the right we
;; advance POS and try again, i.e. we go
;; back to the start of the outer LOOP
(setq pos new-pos)
;; this means "return from inner LOOP"
(return))
(t
;; otherwise NEW-POS is smaller than POS,
;; so we have to redo the inner LOOP to
;; find another end string candidate
;; further to the right
(setq try-pos (1+ *end-string-pos*))))))))))))
((and start-string-test end-string-test)
;; we know that the regular expression has constant start and
;; end strings; similar to the first case but we only need to
;; check for the end string, it doesn't provide enough
;; information to advance POS
(insert-advance-fn
(advance-fn (pos)
(declare (function start-string-test end-string-test))
(unless (setq pos (funcall start-string-test pos))
(return-from scan nil))
(if (<= (the fixnum pos)
(the fixnum *end-string-pos*))
(return-from advance-fn pos))
(unless (setq *end-string-pos* (funcall end-string-test pos))
(return-from scan nil))
pos)))
((and starts-with-everything end-string-test)
;; we know that the regular expression starts with ".*" (which
;; is not in single-line-mode, see CREATE-SCANNER-AUX) and ends
;; with a constant end string; similar to the second case but we
;; only need to check for the end string, it doesn't provide
;; enough information to advance POS
(insert-advance-fn
(advance-fn (pos)
(declare (function end-string-test))
(unless (setq pos (newline-skipper pos))
(return-from scan nil))
(if (<= (the fixnum pos)
(the fixnum *end-string-pos*))
(return-from advance-fn pos))
(unless (setq *end-string-pos* (funcall end-string-test pos))
(return-from scan nil))
pos)))
(start-string-test
;; just check for constant start string candidate
(insert-advance-fn
(advance-fn (pos)
(declare (function start-string-test))
(unless (setq pos (funcall start-string-test pos))
(return-from scan nil))
pos)))
(starts-with-everything
;; just advance POS with NEWLINE-SKIPPER
(insert-advance-fn
(advance-fn (pos)
(unless (setq pos (newline-skipper pos))
(return-from scan nil))
pos)))
(end-string-test
;; just check for the next end string candidate if POS has
;; advanced beyond the last one
(insert-advance-fn
(advance-fn (pos)
(declare (function end-string-test))
(if (<= (the fixnum pos)
(the fixnum *end-string-pos*))
(return-from advance-fn pos))
(unless (setq *end-string-pos* (funcall end-string-test pos))
(return-from scan nil))
pos)))
(t
;; not enough optimization information about the regular
;; expression to optimize so we just return POS
(insert-advance-fn
(advance-fn (pos)
pos))))))

View file

@ -0,0 +1,172 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/specials.lisp,v 1.43 2009/10/28 07:36:15 edi Exp $
;;; globally declared special variables
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-ppcre)
;;; special variables used to effect declarations
(defvar *standard-optimize-settings*
'(optimize
speed
(space 0)
(debug 1)
(compilation-speed 0))
"The standard optimize settings used by most declaration expressions.")
(defvar *special-optimize-settings*
'(optimize speed space)
"Special optimize settings used only by a few declaration expressions.")
;;; special variables used by the lexer/parser combo
(defvar *extended-mode-p* nil
"Whether the parser will start in extended mode.")
(declaim (boolean *extended-mode-p*))
;;; special variables used by the SCAN function and the matchers
(defvar *regex-char-code-limit* char-code-limit
"The upper exclusive bound on the char-codes of characters which can
occur in character classes. Change this value BEFORE creating
scanners if you don't need the \(full) Unicode support of
implementations like AllegroCL, CLISP, LispWorks, or SBCL.")
(declaim (fixnum *regex-char-code-limit*))
(defvar *string* (make-sequence #+:lispworks 'lw:simple-text-string
#-:lispworks 'simple-string
0)
"The string which is currently scanned by SCAN.
Will always be coerced to a SIMPLE-STRING.")
#+:lispworks
(declaim (lw:simple-text-string *string*))
#-:lispworks
(declaim (simple-string *string*))
(defvar *start-pos* 0
"Where to start scanning within *STRING*.")
(declaim (fixnum *start-pos*))
(defvar *real-start-pos* nil
"The real start of *STRING*. This is for repeated scans and is only used internally.")
(declaim (type (or null fixnum) *real-start-pos*))
(defvar *end-pos* 0
"Where to stop scanning within *STRING*.")
(declaim (fixnum *end-pos*))
(defvar *reg-starts* (make-array 0)
"An array which holds the start positions
of the current register candidates.")
(declaim (simple-vector *reg-starts*))
(defvar *regs-maybe-start* (make-array 0)
"An array which holds the next start positions
of the current register candidates.")
(declaim (simple-vector *regs-maybe-start*))
(defvar *reg-ends* (make-array 0)
"An array which holds the end positions
of the current register candidates.")
(declaim (simple-vector *reg-ends*))
(defvar *end-string-pos* nil
"Start of the next possible end-string candidate.")
(defvar *rep-num* 0
"Counts the number of \"complicated\" repetitions while the matchers
are built.")
(declaim (fixnum *rep-num*))
(defvar *zero-length-num* 0
"Counts the number of repetitions the inner regexes of which may
have zero-length while the matchers are built.")
(declaim (fixnum *zero-length-num*))
(defvar *repeat-counters* (make-array 0
:initial-element 0
:element-type 'fixnum)
"An array to keep track of how often
repetitive patterns have been tested already.")
(declaim (type (array fixnum (*)) *repeat-counters*))
(defvar *last-pos-stores* (make-array 0)
"An array to keep track of the last positions
where we saw repetitive patterns.
Only used for patterns which might have zero length.")
(declaim (simple-vector *last-pos-stores*))
(defvar *use-bmh-matchers* nil
"Whether the scanners created by CREATE-SCANNER should use the \(fast
but large) Boyer-Moore-Horspool matchers.")
(defvar *optimize-char-classes* nil
"Whether character classes should be compiled into look-ups into
O\(1) data structures. This is usually fast but will be costly in
terms of scanner creation time and might be costly in terms of size if
*REGEX-CHAR-CODE-LIMIT* is high. This value will be used as the :KIND
keyword argument to CREATE-OPTIMIZED-TEST-FUNCTION - see there for the
possible non-NIL values.")
(defvar *property-resolver* nil
"Should be NIL or a designator for a function which accepts strings
and returns unary character test functions or NIL. This 'resolver' is
intended to handle `character properties' like \\p{IsAlpha}. If
*PROPERTY-RESOLVER* is NIL, then the parser will simply treat \\p and
\\P as #\\p and #\\P as in older versions of CL-PPCRE.")
(defvar *allow-quoting* nil
"Whether the parser should support Perl's \\Q and \\E.")
(defvar *allow-named-registers* nil
"Whether the parser should support AllegroCL's named registers
\(?<name>\"<regex>\") and back-reference \\k<name> syntax.")
(pushnew :cl-ppcre *features*)
;; stuff for Nikodemus Siivola's HYPERDOC
;; see <http://common-lisp.net/project/hyperdoc/>
;; and <http://www.cliki.net/hyperdoc>
;; also used by LW-ADD-ONS
(defvar *hyperdoc-base-uri* "http://weitz.de/cl-ppcre/")
(let ((exported-symbols-alist
(loop for symbol being the external-symbols of :cl-ppcre
collect (cons symbol
(concatenate 'string
"#"
(string-downcase symbol))))))
(defun hyperdoc-lookup (symbol type)
(declare (ignore type))
(cdr (assoc symbol
exported-symbols-alist
:test #'eq))))

View file

@ -0,0 +1,37 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/test/packages.lisp,v 1.4 2009/09/17 19:17:36 edi Exp $
;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.
;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:
;;; * Redistributions of source code must retain the above copyright
;;; notice, this list of conditions and the following disclaimer.
;;; * Redistributions in binary form must reproduce the above
;;; copyright notice, this list of conditions and the following
;;; disclaimer in the documentation and/or other materials
;;; provided with the distribution.
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(in-package :cl-user)
(defpackage :cl-ppcre-test
#+genera (:shadowing-import-from :common-lisp :lambda)
(:use #-:genera :cl #+:genera :future-common-lisp :cl-ppcre)
(:import-from :cl-ppcre :*standard-optimize-settings*
:string-list-to-simple-string)
(:export :run-all-tests :unicode-test))

Some files were not shown because too many files have changed in this diff Show more