guile-lib-0.2.6.1/ 0000775 0000764 0000764 00000000000 13320656106 010544 5 0000000 0000000 guile-lib-0.2.6.1/src/ 0000775 0000764 0000764 00000000000 13320656103 011330 5 0000000 0000000 guile-lib-0.2.6.1/src/texinfo/ 0000775 0000764 0000764 00000000000 13320656103 013004 5 0000000 0000000 guile-lib-0.2.6.1/src/texinfo/nodal-tree.scm 0000664 0000764 0000764 00000010403 13314016560 015460 0000000 0000000 ;; (texinfo nodal-tree) -- rendering stexinfo to a nodal tree
;; Copyright (C) 2003,2004,2011 Andy Wingo
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;; Commentary:
;;
;;This module exports a procedure to chunk a stexi doument into pieces,
;;delimited by sectioning commands (@code{@@chapter},
;;@code{@@appendixsec}, etc.). Note that the sectioning commands must be
;;preceded by a @code{@@node}, a condition that the output of
;;@code{(sxml texinfo)} respects.
;;
;;The output is a nodal tree (see (container nodal-tree)), with the
;;following fields defined for each node:
;;
;;; Code:
(define-module (texinfo nodal-tree)
#:use-module (container nodal-tree)
#:use-module (sxml simple)
#:use-module (scheme kwargs)
#:use-module (texinfo) ;; texi-command-depth
#:export (stexi->nodal-tree))
(define (node? elt)
(and (pair? elt) (eq? (car elt) 'node)))
(define (chunking-section? elt max-depth)
(and (pair? elt) (texi-command-depth (car elt) max-depth)))
(define (append-child! parent kid)
(if parent
(node-set! parent 'children
(append! (node-ref parent 'children) (list kid)))))
(define (find-parent node)
(or (and=> (node-ref node 'parent) find-parent) node))
;; There has to be a more functional way to do this! Probably involves
;; building up from the leaves, instead of building down from the root.
;; Thankfully, the ugliness of this code isn't exported.
(define/kwargs (stexi->nodal-tree (stexi #f) (max-depth 4) (initial-depth 0))
"Break @var{stexi} into a nodal tree. Only break until sectioning
identifiers of depth @var{max-depth}. The following fields are defined
for each node:
@table @code
@item name
The name of the section.
@item value
The content of the section, as @code{stexi}. The containing element is
@code{texinfo}.
@item parent
A reference to the parent node.
@item children
A list of subnodes, corresponding to the subsections of the current
section.
@end table"
(define (make-node* parent tree-title)
(let ((node (make-node
'name (sxml->string tree-title)
'value #f
'parent parent
'children '())))
(append-child! parent node)
node))
(or (eq? (car stexi) 'texinfo) (error "Invalid stexi"))
(let lp ((in stexi)
(val '())
(node (make-node* #f (cadr stexi)))
(parent #f)
(depth initial-depth))
;; (pk (or (null? in) (car in)) val node parent depth)
(cond
((null? in)
(node-set! node 'value (reverse val))
(find-parent node))
((or (chunking-section? (car in) max-depth)
(and (node? (car in)) (pair? in) (pair? (cdr in))
(chunking-section? (cadr in) max-depth)))
(node-set! node 'value (reverse val))
(let* ((node-statement (if (node? (car in)) (car in) #f))
(in (if node-statement (cdr in) in))
(new-depth (texi-command-depth (caar in) max-depth)))
(let new-parent ((parent node) (diff (- new-depth depth)))
(cond
((not parent) (error "invalid stexi"))
((positive? diff)
(or (eq? diff 1)
(error "can only descend by one depth level at a time"
(car in)))
(lp (cdr in)
`(,(car in)
,@(if node-statement (list node-statement) '())
(% (title ,(sxml->string (car in)))) texinfo)
(make-node* parent (car in)) parent new-depth))
(else
(new-parent (node-ref parent 'parent) (1+ diff)))))))
(else
(lp (cdr in) (cons (car in) val) node parent depth)))))
;;; arch-tag: aff19153-493d-4755-ba6f-22cc7fb43c60
guile-lib-0.2.6.1/src/apicheck.scm 0000664 0000764 0000764 00000023460 13314016560 013530 0000000 0000000 ;; (apicheck) -- check for API incompatibilities
;; Copyright (C) 2007, 2013 Andy Wingo
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;; Commentary:
;;
;; @code{(apicheck)} exports two routines. @code{apicheck-generate}
;; produces a description of the Scheme API exported by a set of modules
;; as an S-expression. @code{apicheck-validate} verifies that the API
;; exported by a set of modules is compatible with an API description
;; generated by @code{apicheck-generate}.
;;
;; It would be nice to have Makefile.am fragments here, but for now, see
;; the Guile-Library source distribution for information on how to
;; integrate apicheck with your module's unit test suite.
;;
;;; Code:
(define-module (apicheck)
#:use-module (unit-test)
#:use-module (oop goops)
#:use-module (ice-9 pretty-print)
#:use-module ((ice-9 common-list) #:select (uniq))
#:use-module ((srfi srfi-1) #:select (append-map
lset-difference))
#:export (apicheck-generate apicheck-validate))
(define *path* (make-fluid))
(fluid-set! *path* '())
(define-macro (with-path k v body)
`(with-fluids ((*path* (acons ,k ,v (fluid-ref *path*))))
,body))
(define (write-current-path port)
(for-each (lambda (p)
(format port "In ~a ~a:\n" (car p) (cdr p)))
(fluid-ref *path*)))
(define (fail message . args)
(write-current-path (current-error-port))
(display message (current-error-port))
(for-each (lambda (x)
(display #\space (current-error-port))
(write x (current-error-port)))
args)
(newline (current-error-port))
(error "API changed incompatibly")
(apply error message args))
(define (interface module)
(case (module-kind module)
((interface) module)
(else
(fail "Invalid API: imported module ~a not an interface" module))))
(define (module-all-uses module)
(let ((direct (module-uses module)))
(map interface
(append direct
(apply append (map module-all-uses direct))))))
(define (module-exports module)
(module-map (lambda (k v) k) module))
(define (symbolcomp pred)
(lambda (a b)
(pred (symbol->string a) (symbol->string b))))
(define symbol
(symbolcomp string))
(define symbol>?
(symbolcomp string>?))
(define (symlist a b)
(cond
((null? a) (not (null? b)))
((null? b) #f)
((symbol? a) (or (pair? b) (symbol a b)))
((symbol? b) #f)
((symbol (car a) (car b)) #t)
((symbol>? (car a) (car b)) #f)
(else (symlist (cdr a) (cdr b)))))
(define (module a b)
(symlist (module-name a) (module-name b)))
(define (all-public-interfaces module-names)
(uniq
(sort
(append-map
(lambda (name)
(let ((mod (resolve-interface name)))
(cons mod (module-all-uses mod))))
module-names)
module)))
(define (module-exports-sorted mod)
(sort (hash-fold (lambda (k v rest) (cons k rest)) '()
(module-obarray mod))
symbol))
(define (module-map-sorted proc mod)
(let ((obarray (module-obarray mod)))
(map (lambda (sym)
(proc sym (hashq-ref obarray sym)))
(module-exports-sorted mod))))
(define (procedure-arity proc)
(cond-expand
(guile-2
(cons 'arity (procedure-minimum-arity proc)))
(else
(assq 'arity (procedure-properties proc)))))
;; deals with improper lists
(define (map* proc l)
(cond ((null? l) '())
((pair? l) (cons (proc (car l)) (map* proc (cdr l))))
(else (proc l))))
(define (method-specializer-names method)
(map* class-name (method-specializers method)))
(define (variable-type sym var)
(let ((val (catch #t
(lambda () (variable-ref var))
(lambda args (fail "unbound variable" sym)))))
(cond
((is-a? val ) (list 'class))
((is-a? val ) (cons 'generic
(sort
(map
method-specializer-names
(generic-function-methods val))
symlist)))
((procedure? val) (list 'procedure (procedure-arity val)))
((macro? val) (list 'macro))
((struct-vtable? val) (list 'struct-vtable))
(else (list (class-name (class-of val)))))))
(define (module-api module)
`(,(module-name module)
(uses-interfaces
,@(map module-name (sort (module-uses module) module)))
(typed-exports
,@(module-map-sorted
(lambda (sym var)
(cons sym (variable-type sym var)))
module))))
(define *apicheck-major-version* 1)
(define *apicheck-minor-version* 0)
(define (apicheck-generate module-names)
"Generate a description of the API exported by the set of modules
@var{module-names}."
(cons*
'module-api
(list 'version *apicheck-major-version* *apicheck-minor-version*)
(map module-api
(all-public-interfaces module-names))))
(define (form-match? form template)
(define (pred? x)
(procedure? x))
(define (var? x)
(eq? x '_))
(define (atom? x)
(not (pair? x)))
(define (pred-match? pred form)
(pred form))
(define (var-match? var form)
#t)
(define (atom-match? atom form)
(eq? atom form))
(cond ((null? template) (null? form))
((pred? template) (pred-match? template form))
((var? template) (var-match? template form))
((atom? form) (atom-match? template form))
(else (and (form-match? (car form) (car template))
(form-match? (cdr form) (cdr template))))))
(define (apicheck-form? form)
(form-match? form `(module-api
(version ,number? ,number?)
. _)))
(define (apicheck-version-compatible? form)
(let ((version-form (cadr form)))
(and (= (cadr version-form) *apicheck-major-version*)
(<= (caddr version-form) *apicheck-minor-version*))))
(define (assert-sets-compatible! expected actual)
(let ((new (lset-difference equal? actual expected)))
(if (not (null? new))
(warn "New API, update your API form" new)))
(let ((missing (lset-difference equal? expected actual)))
(if (not (null? missing))
(fail "Public API has been removed" missing))))
(define (arities-compatible? old new)
;; arity := (arity nrequired noptional rest?)
(define (required x)
(cadr x))
(define (optional x)
(caddr x))
(define (rest? x)
(cadddr x))
(and (cond ((< (required old) (required new)) #f)
((= (required old) (required new)) #t)
(else (or (rest? new)
(<= (- (required old) (required new))
(- (optional new) (optional old))))))
(or (<= (required old) (required new))
(rest? new))
(if (rest? old) (rest? new) #t)))
(define (method-specializers-compatible? old new)
;; FIXME: define better
(assert-sets-compatible! old new))
(define (apicheck-validate-var-type type-form var)
(let ((name (car type-form))
(expected-type (cadr type-form))
(expected-args (cddr type-form)))
(let ((actual (variable-type name var)))
(let ((actual-type (car actual))
(actual-args (cdr actual)))
(or (eq? expected-type actual-type)
(fail "API break: export changed type"
name expected-type actual-type))
(or (case expected-type
((generic)
(pk name expected-args actual-args)
(method-specializers-compatible? expected-args actual-args))
((procedure)
(arities-compatible? (car expected-args) (car actual-args)))
(else ;; pass
#t))
(fail "API break: export changed type incompatibly"
type-form actual))))))
(define (apicheck-validate-module module-form)
(with-path "module" (car module-form)
(let ((interface (resolve-interface (car module-form)))
(uses-interfaces (cdr (assq 'uses-interfaces module-form)))
(typed-exports (cdr (assq 'typed-exports module-form))))
(with-path "re-exported interfaces" uses-interfaces
(assert-sets-compatible!
uses-interfaces
(map module-name (module-uses interface))))
(with-path "exports" (map car typed-exports)
(assert-sets-compatible!
(map car typed-exports)
(module-exports-sorted interface)))
(for-each
(lambda (form)
(with-path "exported binding" (car form)
(apicheck-validate-var-type
form
(module-local-variable interface (car form)))))
typed-exports))))
(define (apicheck-validate api module-names)
"Validate that the API exported by the set of modules
@var{module-names} is compatible with the recorded API description
@var{api}. Raises an exception if the interface is incompatible."
(or (apicheck-form? api)
(error "Invalid apicheck form" api))
(or (apicheck-version-compatible? api)
(error "Invalid apicheck version"
*apicheck-major-version* *apicheck-minor-version* api))
(let ((module-forms (cddr api)))
(with-path "toplevel exports" (map car module-forms)
(assert-sets-compatible!
(map car module-forms)
(map module-name (all-public-interfaces module-names))))
(for-each apicheck-validate-module module-forms)))
guile-lib-0.2.6.1/src/Makefile.in 0000664 0000764 0000764 00000053243 13320656066 013334 0000000 0000000 # Makefile.in generated by automake 1.15.1 from Makefile.am.
# @configure_input@
# Copyright (C) 1994-2017 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
@SET_MAKE@
####
#### Copyright (C) 2016 - 2018 David Pirotte
#### David Pirotte
#### This file is part of Guile-Lib.
#### Guile-Lib is free software: you can redistribute it, as a whole,
#### and/or modify it under the terms of the GNU General Public
#### License as published by the Free Software Foundation, either
#### version 3 of the License, or (at your option) any later version.
#### Each Guile-Lib module contained in Guile-Lib has its own copying
#### conditions, specified in the comments at the beginning of the
#### module's source file.
#### Guile-Lib is distributed in the hope that it will be useful, but
#### WITHOUT ANY WARRANTY; without even the implied warranty of
#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#### General Public License for more details.
#### You should have received a copy of the GNU General Public License
#### along with Guile-Lib. If not, see
#### .
####
####
#### Copyright (C) 2018 David Pirotte
#### David Pirotte
#### This file is part of Guile-Lib.
#### Guile-Lib is free software: you can redistribute it, as a whole,
#### and/or modify it under the terms of the GNU General Public
#### License as published by the Free Software Foundation, either
#### version 3 of the License, or (at your option) any later version.
#### Each Guile-Lib module contained in Guile-Lib has its own copying
#### conditions, specified in the comments at the beginning of the
#### module's source file.
#### Guile-Lib is distributed in the hope that it will be useful, but
#### WITHOUT ANY WARRANTY; without even the implied warranty of
#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#### General Public License for more details.
#### You should have received a copy of the GNU General Public License
#### along with Guile-Lib. If not, see
#### .
####
VPATH = @srcdir@
am__is_gnu_make = { \
if test -z '$(MAKELEVEL)'; then \
false; \
elif test -n '$(MAKE_HOST)'; then \
true; \
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
true; \
else \
false; \
fi; \
}
am__make_running_with_option = \
case $${target_option-} in \
?) ;; \
*) echo "am__make_running_with_option: internal error: invalid" \
"target option '$${target_option-}' specified" >&2; \
exit 1;; \
esac; \
has_opt=no; \
sane_makeflags=$$MAKEFLAGS; \
if $(am__is_gnu_make); then \
sane_makeflags=$$MFLAGS; \
else \
case $$MAKEFLAGS in \
*\\[\ \ ]*) \
bs=\\; \
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
esac; \
fi; \
skip_next=no; \
strip_trailopt () \
{ \
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
}; \
for flg in $$sane_makeflags; do \
test $$skip_next = yes && { skip_next=no; continue; }; \
case $$flg in \
*=*|--*) continue;; \
-*I) strip_trailopt 'I'; skip_next=yes;; \
-*I?*) strip_trailopt 'I';; \
-*O) strip_trailopt 'O'; skip_next=yes;; \
-*O?*) strip_trailopt 'O';; \
-*l) strip_trailopt 'l'; skip_next=yes;; \
-*l?*) strip_trailopt 'l';; \
-[dEDm]) skip_next=yes;; \
-[JT]) skip_next=yes;; \
esac; \
case $$flg in \
*$$target_option*) has_opt=yes; break;; \
esac; \
done; \
test $$has_opt = yes
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
pkgdatadir = $(datadir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkglibexecdir = $(libexecdir)/@PACKAGE@
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
INSTALL_HEADER = $(INSTALL_DATA)
transform = $(program_transform_name)
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
subdir = src
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/guile-ext.m4 \
$(top_srcdir)/m4/guile.m4 $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
mkinstalldirs = $(install_sh) -d
CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false
am__v_P_1 = :
AM_V_GEN = $(am__v_GEN_@AM_V@)
am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
am__v_GEN_0 = @echo " GEN " $@;
am__v_GEN_1 =
AM_V_at = $(am__v_at_@AM_V@)
am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
am__v_at_0 = @
am__v_at_1 =
DIST_SOURCES =
am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \
n|no|NO) false;; \
*) (install-info --version) >/dev/null 2>&1;; \
esac
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
am__vpath_adj = case $$p in \
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
*) f=$$p;; \
esac;
am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
am__install_max = 40
am__nobase_strip_setup = \
srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
am__nobase_strip = \
for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
am__nobase_list = $(am__nobase_strip_setup); \
for p in $$list; do echo "$$p $$p"; done | \
sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
$(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
if (++n[$$2] == $(am__install_max)) \
{ print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
END { for (dir in files) print dir, files[dir] }'
am__base_list = \
sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
am__uninstall_files_from_dir = { \
test -z "$$files" \
|| { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
|| { echo " ( cd '$$dir' && rm -f" $$files ")"; \
$(am__cd) "$$dir" && rm -f $$files; }; \
}
am__installdirs = "$(DESTDIR)$(godir)" "$(DESTDIR)$(moddir)"
DATA = $(nobase_go_DATA) $(nobase_mod_DATA)
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
# Read a list of newline-separated strings from the standard input,
# and print each of them once, without duplicates. Input order is
# *not* preserved.
am__uniquify_input = $(AWK) '\
BEGIN { nonempty = 0; } \
{ items[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in items) print i; }; } \
'
# Make sure the list of sources is unique. This is necessary because,
# e.g., the same source file might be shared among _SOURCES variables
# for different programs/libraries.
am__define_uniq_tagged_files = \
list='$(am__tagged_files)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | $(am__uniquify_input)`
ETAGS = etags
CTAGS = ctags
am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/am/guile.mk
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
ACLOCAL = @ACLOCAL@
ACLOCAL_FLAGS = @ACLOCAL_FLAGS@
AMTAR = @AMTAR@
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
AUTOCONF = @AUTOCONF@
AUTOHEADER = @AUTOHEADER@
AUTOMAKE = @AUTOMAKE@
AWK = @AWK@
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CYGPATH_W = @CYGPATH_W@
DEFS = @DEFS@
DEPDIR = @DEPDIR@
ECHO_C = @ECHO_C@
ECHO_N = @ECHO_N@
ECHO_T = @ECHO_T@
EGREP = @EGREP@
EXEEXT = @EXEEXT@
GREP = @GREP@
GUILD = @GUILD@
GUILE = @GUILE@
GUILE_CFLAGS = @GUILE_CFLAGS@
GUILE_CONFIG = @GUILE_CONFIG@
GUILE_EFFECTIVE_VERSION = @GUILE_EFFECTIVE_VERSION@
GUILE_EXTENSION = @GUILE_EXTENSION@
GUILE_GLOBAL_SITE = @GUILE_GLOBAL_SITE@
GUILE_LDFLAGS = @GUILE_LDFLAGS@
GUILE_LIBS = @GUILE_LIBS@
GUILE_LTLIBS = @GUILE_LTLIBS@
GUILE_SITE = @GUILE_SITE@
GUILE_SITE_CCACHE = @GUILE_SITE_CCACHE@
GUILE_TOOLS = @GUILE_TOOLS@
INSTALL = @INSTALL@
INSTALL_DATA = @INSTALL_DATA@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
LDFLAGS = @LDFLAGS@
LIBOBJS = @LIBOBJS@
LIBS = @LIBS@
LTLIBOBJS = @LTLIBOBJS@
MAINT = @MAINT@
MAKEINFO = @MAKEINFO@
MKDIR_P = @MKDIR_P@
OBJEXT = @OBJEXT@
PACKAGE = @PACKAGE@
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_STRING = @PACKAGE_STRING@
PACKAGE_TARNAME = @PACKAGE_TARNAME@
PACKAGE_URL = @PACKAGE_URL@
PACKAGE_VERSION = @PACKAGE_VERSION@
PATH_SEPARATOR = @PATH_SEPARATOR@
PKG_CONFIG = @PKG_CONFIG@
PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
SITECCACHEDIR = @SITECCACHEDIR@
SITEDIR = @SITEDIR@
STRIP = @STRIP@
VERSION = @VERSION@
abs_builddir = @abs_builddir@
abs_srcdir = @abs_srcdir@
abs_top_builddir = @abs_top_builddir@
abs_top_srcdir = @abs_top_srcdir@
ac_ct_CC = @ac_ct_CC@
am__include = @am__include@
am__leading_dot = @am__leading_dot@
am__quote = @am__quote@
am__tar = @am__tar@
am__untar = @am__untar@
bindir = @bindir@
build = @build@
build_alias = @build_alias@
build_cpu = @build_cpu@
build_os = @build_os@
build_vendor = @build_vendor@
builddir = @builddir@
datadir = @datadir@
datarootdir = @datarootdir@
docdir = @docdir@
dvidir = @dvidir@
exec_prefix = @exec_prefix@
guile_site = @guile_site@
host = @host@
host_alias = @host_alias@
host_cpu = @host_cpu@
host_os = @host_os@
host_vendor = @host_vendor@
htmldir = @htmldir@
includedir = @includedir@
infodir = @infodir@
install_sh = @install_sh@
libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
pdfdir = @pdfdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
runstatedir = @runstatedir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
sysconfdir = @sysconfdir@
target_alias = @target_alias@
top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
moddir = @SITEDIR@
godir = @SITECCACHEDIR@
GOBJECTS = $(SOURCES:%.scm=%.go)
nobase_mod_DATA = $(SOURCES) $(NOCOMP_SOURCES)
nobase_go_DATA = $(GOBJECTS)
# Make sure source files are installed first, so that the mtime of
# installed compiled files is greater than that of installed source
# files. See
#
# for details.
guile_install_go_files = install-nobase_goDATA
GUILE_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat
SUFFIXES = .scm .go
SXML_FILES = \
sxml/apply-templates.scm \
sxml/fold.scm \
sxml/simple.scm \
sxml/ssax/input-parse.scm \
sxml/ssax.scm \
sxml/transform.scm \
sxml/unicode.scm \
sxml/upstream/SSAX-expanded.scm \
sxml/upstream/SSAX.scm \
sxml/upstream/SXML-tree-trans.scm \
sxml/upstream/SXPath-old.scm \
sxml/upstream/input-parse.scm \
sxml/upstream/assert.scm \
sxml/xpath.scm \
sxml/ssax-simple.scm
# Note, texinfo/nodal-tree.scm is not in guile 2.0, so it is in SOURCES
# below.
TEXINFO_FILES = \
texinfo/docbook.scm \
texinfo/html.scm \
texinfo/indexing.scm \
texinfo/plain-text.scm \
texinfo/reflection.scm \
texinfo/serialize.scm \
texinfo.scm
STATPROF_FILES = \
statprof.scm
# Guile-Lib depends on Guile 2.0, and Guile 2.0 already comes with the
# `(sxml ...)' and `(texinfo ...)' modules unmodified. We don't need
# the following variables anymore, also removed from the SOURCES
# definition below.
# SXML_SOURCES =
# TEXINFO_SOURCES =
# STATPROF_SOURCES =
SOURCES = \
apicheck.scm \
compat/guile-2.scm \
config/load.scm \
container/delay-tree.scm \
container/nodal-tree.scm \
container/async-queue.scm \
debugging/assert.scm \
debugging/time.scm \
graph/topological-sort.scm \
htmlprag.scm \
io/string.scm \
logging/logger.scm \
logging/port-log.scm \
logging/rotating-log.scm \
math/minima.scm \
math/primes.scm \
match-bind.scm \
md5.scm \
os/process.scm \
scheme/documentation.scm \
scheme/kwargs.scm \
search/basic.scm \
string/completion.scm \
string/soundex.scm \
string/transform.scm \
string/wrap.scm \
term/ansi-color.scm \
texinfo/nodal-tree.scm \
text/parse-lalr.scm \
unit-test.scm
EXTRA_DIST = \
$(SOURCES) \
$(NOCOMP_SOURCES)
CLEANFILES = $(GOBJECTS)
all: all-am
.SUFFIXES:
.SUFFIXES: .scm .go
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/guile.mk $(am__configure_deps)
@for dep in $?; do \
case '$(am__configure_deps)' in \
*$$dep*) \
( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
&& { if test -f $@; then exit 0; else break; fi; }; \
exit 1;; \
esac; \
done; \
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --foreign src/Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
@case '$?' in \
*config.status*) \
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
*) \
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
esac;
$(top_srcdir)/am/guile.mk $(am__empty):
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(am__aclocal_m4_deps):
install-nobase_goDATA: $(nobase_go_DATA)
@$(NORMAL_INSTALL)
@list='$(nobase_go_DATA)'; test -n "$(godir)" || list=; \
if test -n "$$list"; then \
echo " $(MKDIR_P) '$(DESTDIR)$(godir)'"; \
$(MKDIR_P) "$(DESTDIR)$(godir)" || exit 1; \
fi; \
$(am__nobase_list) | while read dir files; do \
xfiles=; for file in $$files; do \
if test -f "$$file"; then xfiles="$$xfiles $$file"; \
else xfiles="$$xfiles $(srcdir)/$$file"; fi; done; \
test -z "$$xfiles" || { \
test "x$$dir" = x. || { \
echo " $(MKDIR_P) '$(DESTDIR)$(godir)/$$dir'"; \
$(MKDIR_P) "$(DESTDIR)$(godir)/$$dir"; }; \
echo " $(INSTALL_DATA) $$xfiles '$(DESTDIR)$(godir)/$$dir'"; \
$(INSTALL_DATA) $$xfiles "$(DESTDIR)$(godir)/$$dir" || exit $$?; }; \
done
uninstall-nobase_goDATA:
@$(NORMAL_UNINSTALL)
@list='$(nobase_go_DATA)'; test -n "$(godir)" || list=; \
$(am__nobase_strip_setup); files=`$(am__nobase_strip)`; \
dir='$(DESTDIR)$(godir)'; $(am__uninstall_files_from_dir)
install-nobase_modDATA: $(nobase_mod_DATA)
@$(NORMAL_INSTALL)
@list='$(nobase_mod_DATA)'; test -n "$(moddir)" || list=; \
if test -n "$$list"; then \
echo " $(MKDIR_P) '$(DESTDIR)$(moddir)'"; \
$(MKDIR_P) "$(DESTDIR)$(moddir)" || exit 1; \
fi; \
$(am__nobase_list) | while read dir files; do \
xfiles=; for file in $$files; do \
if test -f "$$file"; then xfiles="$$xfiles $$file"; \
else xfiles="$$xfiles $(srcdir)/$$file"; fi; done; \
test -z "$$xfiles" || { \
test "x$$dir" = x. || { \
echo " $(MKDIR_P) '$(DESTDIR)$(moddir)/$$dir'"; \
$(MKDIR_P) "$(DESTDIR)$(moddir)/$$dir"; }; \
echo " $(INSTALL_DATA) $$xfiles '$(DESTDIR)$(moddir)/$$dir'"; \
$(INSTALL_DATA) $$xfiles "$(DESTDIR)$(moddir)/$$dir" || exit $$?; }; \
done
uninstall-nobase_modDATA:
@$(NORMAL_UNINSTALL)
@list='$(nobase_mod_DATA)'; test -n "$(moddir)" || list=; \
$(am__nobase_strip_setup); files=`$(am__nobase_strip)`; \
dir='$(DESTDIR)$(moddir)'; $(am__uninstall_files_from_dir)
ID: $(am__tagged_files)
$(am__define_uniq_tagged_files); mkid -fID $$unique
tags: tags-am
TAGS: tags
tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
set x; \
here=`pwd`; \
$(am__define_uniq_tagged_files); \
shift; \
if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
test -n "$$unique" || unique=$$empty_fix; \
if test $$# -gt 0; then \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
"$$@" $$unique; \
else \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
$$unique; \
fi; \
fi
ctags: ctags-am
CTAGS: ctags
ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
$(am__define_uniq_tagged_files); \
test -z "$(CTAGS_ARGS)$$unique" \
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
$$unique
GTAGS:
here=`$(am__cd) $(top_builddir) && pwd` \
&& $(am__cd) $(top_srcdir) \
&& gtags -i $(GTAGS_ARGS) "$$here"
cscopelist: cscopelist-am
cscopelist-am: $(am__tagged_files)
list='$(am__tagged_files)'; \
case "$(srcdir)" in \
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
*) sdir=$(subdir)/$(srcdir) ;; \
esac; \
for i in $$list; do \
if test -f "$$i"; then \
echo "$(subdir)/$$i"; \
else \
echo "$$sdir/$$i"; \
fi; \
done >> $(top_builddir)/cscope.files
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
distdir: $(DISTFILES)
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
list='$(DISTFILES)'; \
dist_files=`for file in $$list; do echo $$file; done | \
sed -e "s|^$$srcdirstrip/||;t" \
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
case $$dist_files in \
*/*) $(MKDIR_P) `echo "$$dist_files" | \
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
sort -u` ;; \
esac; \
for file in $$dist_files; do \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
if test -d $$d/$$file; then \
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
if test -d "$(distdir)/$$file"; then \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
else \
test -f "$(distdir)/$$file" \
|| cp -p $$d/$$file "$(distdir)/$$file" \
|| exit 1; \
fi; \
done
check-am: all-am
check: check-am
all-am: Makefile $(DATA)
installdirs:
for dir in "$(DESTDIR)$(godir)" "$(DESTDIR)$(moddir)"; do \
test -z "$$dir" || $(MKDIR_P) "$$dir"; \
done
install: install-am
install-exec: install-exec-am
install-data: install-data-am
uninstall: uninstall-am
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
installcheck: installcheck-am
install-strip:
if test -z '$(STRIP)'; then \
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
install; \
else \
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
fi
mostlyclean-generic:
clean-generic:
-test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
distclean-generic:
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
maintainer-clean-generic:
@echo "This command is intended for maintainers to use"
@echo "it deletes files that may require special tools to rebuild."
clean: clean-am
clean-am: clean-generic mostlyclean-am
distclean: distclean-am
-rm -f Makefile
distclean-am: clean-am distclean-generic distclean-tags
dvi: dvi-am
dvi-am:
html: html-am
html-am:
info: info-am
info-am:
install-data-am: install-nobase_goDATA install-nobase_modDATA
install-dvi: install-dvi-am
install-dvi-am:
install-exec-am:
install-html: install-html-am
install-html-am:
install-info: install-info-am
install-info-am:
install-man:
install-pdf: install-pdf-am
install-pdf-am:
install-ps: install-ps-am
install-ps-am:
installcheck-am:
maintainer-clean: maintainer-clean-am
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-am
mostlyclean-am: mostlyclean-generic
pdf: pdf-am
pdf-am:
ps: ps-am
ps-am:
uninstall-am: uninstall-nobase_goDATA uninstall-nobase_modDATA
.MAKE: install-am install-strip
.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
cscopelist-am ctags ctags-am distclean distclean-generic \
distclean-tags distdir dvi dvi-am html html-am info info-am \
install install-am install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-html \
install-html-am install-info install-info-am install-man \
install-nobase_goDATA install-nobase_modDATA install-pdf \
install-pdf-am install-ps install-ps-am install-strip \
installcheck installcheck-am installdirs maintainer-clean \
maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
pdf-am ps ps-am tags tags-am uninstall uninstall-am \
uninstall-nobase_goDATA uninstall-nobase_modDATA
.PRECIOUS: Makefile
$(guile_install_go_files): install-nobase_modDATA
.scm.go:
$(AM_V_GEN)$(top_builddir)/pre-inst-env \
guild compile $(GUILE_WARNINGS) -o "$@" "$<"
printenv:
printf '$(moddir)\n$(godir)\n'
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
guile-lib-0.2.6.1/src/io/ 0000775 0000764 0000764 00000000000 13320656103 011737 5 0000000 0000000 guile-lib-0.2.6.1/src/io/string.scm 0000664 0000764 0000764 00000007546 13314016560 013705 0000000 0000000 ;; (io string) -- input and output with strings
;; Written 1995, 1996 by Oleg Kiselyov (oleg@acm.org)
;; Modified 1996, 1997, 1998, 2001 by A. Jaffer (agj@alum.mit.edu)
;; Modified 2003 by Steve VanDevender (stevev@hexadecimal.uoregon.edu)
;; Modified 2004 Andy Wingo
;; This file is based SLIB's strsrch.scm, and is in the public domain.
;;; Commentary:
;;
;;@c texinfo, really
;; Procedures that do io with strings.
;;
;;; Code:
(define-module (io string)
#:use-module (scheme documentation)
#:export (find-string-from-port?))
(define-with-docs find-string-from-port?
"Looks for @var{str} in @var{}, optionally within the
first @var{max-no-char} characters."
(lambda (str . max-no-char)
(set! max-no-char (if (null? max-no-char) #f (car max-no-char)))
(letrec
((no-chars-read 0)
(peeked? #f)
(my-peek-char ; Return a peeked char or #f
(lambda () (and (or (not (number? max-no-char))
(< no-chars-read max-no-char))
(let ((c (peek-char )))
(cond (peeked? c)
((eof-object? c) #f)
((procedure? max-no-char)
(set! peeked? #t)
(if (max-no-char c) #f c))
((eqv? max-no-char c) #f)
(else c))))))
(next-char (lambda () (set! peeked? #f) (read-char )
(set! no-chars-read (+ 1 no-chars-read))))
(match-1st-char ; of the string str
(lambda ()
(let ((c (my-peek-char)))
(and c
(begin (next-char)
(if (char=? c (string-ref str 0))
(match-other-chars 1)
(match-1st-char)))))))
;; There has been a partial match, up to the point pos-to-match
;; (for example, str[0] has been found in the stream)
;; Now look to see if str[pos-to-match] for would be found, too
(match-other-chars
(lambda (pos-to-match)
(if (>= pos-to-match (string-length str))
no-chars-read ; the entire string has matched
(let ((c (my-peek-char)))
(and c
(if (not (char=? c (string-ref str pos-to-match)))
(backtrack 1 pos-to-match)
(begin (next-char)
(match-other-chars (+ 1 pos-to-match)))))))))
;; There had been a partial match, but then a wrong char showed up.
;; Before discarding previously read (and matched) characters, we check
;; to see if there was some smaller partial match. Note, characters read
;; so far (which matter) are those of str[0..matched-substr-len - 1]
;; In other words, we will check to see if there is such i>0 that
;; substr(str,0,j) = substr(str,i,matched-substr-len)
;; where j=matched-substr-len - i
(backtrack
(lambda (i matched-substr-len)
(let ((j (- matched-substr-len i)))
(if (<= j 0)
;; backed off completely to the begining of str
(match-1st-char)
(let loop ((k 0))
(if (>= k j)
(match-other-chars j) ; there was indeed a shorter match
(if (char=? (string-ref str k)
(string-ref str (+ i k)))
(loop (+ 1 k))
(backtrack (+ 1 i) matched-substr-len))))))))
)
(match-1st-char))))
;;; arch-tag: 99289f4f-5fdb-4c6e-924a-1c510a61a03e
;;; string.scm ends here
guile-lib-0.2.6.1/src/htmlprag.scm 0000664 0000764 0000764 00000304017 13314016560 013577 0000000 0000000 ;; (htmlprag) -- pragmatic parsing of real-world HTML
;; Copyright (C) 2003-2004 Neil W. Van Dyke
;; Modified 2004 by Andy Wingo to fit in with guile-lib.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Lesser General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this program. If not, see
;; .
;; The license of the code that this is based on, when it came from Neil
;; W. Van Dyke, was the LGPL version 2.1. Neil notes that other
;; licensing options for his code are available; interested parties
;; should contact him directly.
;;; Commentary:
;;
;;; HtmlPrag provides permissive HTML parsing capability to Scheme programs,
;;; which is useful for software agent extraction of information from Web
;;; pages, for programmatically transforming HTML files, and for implementing
;;; interactive Web browsers. HtmlPrag emits ``SHTML,'' which is an encoding
;;; of HTML in [SXML], so that conventional HTML may be processed with XML
;;; tools such as [SXPath] and [SXML-Tools]. Like [SSAX-HTML], HtmlPrag
;;; provides a permissive tokenizer, but also attempts to recover structure.
;;; HtmlPrag also includes procedures for encoding SHTML in HTML syntax.
;;;
;;; The HtmlPrag parsing behavior is permissive in that it accepts erroneous
;;; HTML, handling several classes of HTML syntax errors gracefully, without
;;; yielding a parse error. This is crucial for parsing arbitrary real-world
;;; Web pages, since many pages actually contain syntax errors that would
;;; defeat a strict or validating parser. HtmlPrag's handling of errors is
;;; intended to generally emulate popular Web browsers' interpretation of the
;;; structure of erroneous HTML. We euphemistically term this kind of parse
;;; ``pragmatic.''
;;;
;;; HtmlPrag also has some support for [XHTML], although XML namespace
;;; qualifiers [XML-Names] are currently accepted but stripped from the
;;; resulting SHTML. Note that valid XHTML input is of course better handled
;;; by a validating XML parser like [SSAX].
;;;
;;; To receive notification of new versions of HtmlPrag, and to be polled for
;;; input on changes to HtmlPrag being considered, ask the author to add you to
;;; the moderated, announce-only email list, @code{htmlprag-announce}.
;;;
;;; Thanks to Oleg Kiselyov and Kirill Lisovsky for their help with SXML.
;;
;;; Code:
(define-module (htmlprag))
;; Exports defined at the end of the file
;; THIS FILE GENERATED Thu May 13 21:41:40 EDT 2004 -- DO NOT EDIT MANUALLY
;; ############# BEGIN CANONICAL htmlprag.scm #############
;;; @Package HtmlPrag
;;; @Subtitle Pragmatic Parsing of HTML to SHTML and SXML
;;; @HomePage http://www.neilvandyke.org/htmlprag/
;;; @Author Neil W. Van Dyke
;;; @AuthorEmail neil@@neilvandyke.org
;;; @Version 0.11
;;; @Date 13 May 2004
;; $Id: htmlprag.scm,v 1.304 2004/05/14 01:28:51 neil Exp $
;;; @legal
;;; Copyright @copyright{} 2003-2004 Neil W. Van Dyke. This program is Free
;;; Software; you can redistribute it and/or modify it under the terms of the
;;; GNU Lesser General Public License as published by the Free Software
;;; Foundation; either version 2.1 of the License, or (at your option) any
;;; later version. This program is distributed in the hope that it will be
;;; useful, but without any warranty; without even the implied warranty of
;;; merchantability or fitness for a particular purpose. See the GNU Lesser
;;; General Public License [LGPL] for more details. For other license options
;;; and commercial consulting, contact the author.
;;; @end legal
;;; @section Introduction
;;; HtmlPrag provides permissive HTML parsing capability to Scheme programs,
;;; which is useful for software agent extraction of information from Web
;;; pages, for programmatically transforming HTML files, and for implementing
;;; interactive Web browsers. HtmlPrag emits ``SHTML,'' which is an encoding
;;; of HTML in [SXML], so that conventional HTML may be processed with XML
;;; tools such as [SXPath] and [SXML-Tools]. Like [SSAX-HTML], HtmlPrag
;;; provides a permissive tokenizer, but also attempts to recover structure.
;;; HtmlPrag also includes procedures for encoding SHTML in HTML syntax.
;;;
;;; The HtmlPrag parsing behavior is permissive in that it accepts erroneous
;;; HTML, handling several classes of HTML syntax errors gracefully, without
;;; yielding a parse error. This is crucial for parsing arbitrary real-world
;;; Web pages, since many pages actually contain syntax errors that would
;;; defeat a strict or validating parser. HtmlPrag's handling of errors is
;;; intended to generally emulate popular Web browsers' interpretation of the
;;; structure of erroneous HTML. We euphemistically term this kind of parse
;;; ``pragmatic.''
;;;
;;; HtmlPrag also has some support for [XHTML], although XML namespace
;;; qualifiers [XML-Names] are currently accepted but stripped from the
;;; resulting SHTML. Note that valid XHTML input is of course better handled
;;; by a validating XML parser like [SSAX].
;;;
;;; To receive notification of new versions of HtmlPrag, and to be polled for
;;; input on changes to HtmlPrag being considered, ask the author to add you to
;;; the moderated, announce-only email list, @code{htmlprag-announce}.
;;;
;;; Thanks to Oleg Kiselyov and Kirill Lisovsky for their help with SXML.
;;; @section Portability
;;; HtmlPrag officially requires R5RS, [SRFI-6], and [SRFI-23], but is known to
;;; also work on some non-R5RS implementations. The current version tests
;;; successfully under Bigloo 2.6d, Chicken 1.22, Gauche 0.7.4.2, Guile 1.6.4,
;;; MIT Scheme 7.7.90, PLT MzScheme 206p1, RScheme 0.7.3.3-b20, SISC 1.8.7
;;; (using Kaffe 1.1.4), and STklos 0.55. With a minor change to the source
;;; code, HtmlPrag also tests successfully under Scheme 48 0.57 and Scsh 0.6.3.
;;;
;;; Kawa has been removed temporarily from the test list, but should run if
;;; Sun's Java implementation can be used. SXM has removed temporarily from
;;; the test list, until the test suite code can be adjusted to not exceed
;;; SXM's limit on literals.
;; TODO: Note about packagings.
;;
;; Some packagings of HtmlPrag for particular Scheme implementations are
;; available from the HtmlPrag Web page and elsewhere.
;; TODO: Note conventional prefix option with module systems that support it.
;;
;; @lisp
;; (require (prefix htmlprag: (lib "htmlprag.ss" "htmlprag")))
;; @end lisp
;;; In addition to the documented public bindings, the HtmlPrag source code
;;; includes some internal-use-only toplevel bindings. The names of these
;;; begin with the ``@code{htmlprag-internal:}'' prefix. Packagings of
;;; HtmlPrag for particular Scheme implementations should suppress these
;;; bindings from export when possible.
;; The following bindings are used internally by HtmlPrag for portability,
;; with the intention that packagings of HtmlPrag use faster or more
;; appropriate bindings for the particular Scheme implementation.
;; @defproc htmlprag-internal:a2c num
;;
;; Returns the character with ASCII value @var{num}. In most Scheme
;; implementations, this is the same as @code{integer->char}. Two exceptions
;; are Scheme 48 0.57 and Scsh 0.6.3, for which the user must manually edit
;; file @code{htmlprag.scm} to bind this variable to @code{ascii->char}. A
;; future version of HtmlPrag will automatically use @code{ascii->char} where
;; available.
(define htmlprag-internal:a2c integer->char)
;; @defproc htmlprag-internal:append! a b
;;
;; Returns a concatenation of lists @var{a} and @var{b}, modifying the tail of
;; @var{a} to point to the head of @var{b} if both lists are non-null. A
;; future version should use the more general @code{append!} where available.
(define (htmlprag-internal:append! a b)
(cond ((null? a) b)
((null? b) a)
(else (let loop ((sub a))
(if (null? (cdr sub))
(begin (set-cdr! sub b)
a)
(loop (cdr sub)))))))
;; @defproc htmlprag-internal:reverse!ok lst
;;
;; Returns a reversed list @var{lst}, possibly destructive. A future version
;; will use @code{reverse!} where available, and @code{reverse} elsewhere.
(define htmlprag-internal:reverse!ok reverse)
;; @defproc htmlprag-internal:down str
;;
;; Returns a string that is equivalent to @var{str} with all characters mapped
;; to lowercase, as if by @code{char-downcase}, without mutating @var{str}. A
;; future version should use the Scheme implementation's native nondestructive
;; procedure where available.
(define (htmlprag-internal:down s)
(list->string (map char-downcase (string->list s))))
;; @defproc htmlprag-internal:error proc-str msg obj
;;
;; For Bigloo, this is changed to:
;;
;; @lisp
;; (define htmlprag-internal:error error)
;; @end lisp
(define (htmlprag-internal:error p m o) (error (string-append p " - " m) o))
;; TODO: Make htmlprag-internal:error be syntax.
;; @defproc htmlprag-internal:down!ok str
;;
;; Returns a string that is equivalent to @var{str} with all characters mapped
;; to lowercase, as if by @code{char-downcase}, possibly mutating @var{str}.
;; A future version should use the Scheme implementation's native destructive
;; or nondestructive procedure where available.
(define htmlprag-internal:down!ok htmlprag-internal:down)
;; @defproc htmlprag-internal:gosc os
;;
;; One-shot version of the conventional @code{get-output-string}. The result
;; of any subsequent attempt to write to the port or get the output string is
;; undefined. This may or may not free up resources.
(define (htmlprag-internal:gosc os)
(let ((str (get-output-string os)))
;; Note: By default, we don't call close-output-port, since at least one
;; tested Scheme implementation barfs on that.
;;
;; (close-output-port os)
str))
;; @defvar htmlprag-internal:at
;;
;; Constant bound to the symbol @code{@@}. This is to make code portable to
;; Scheme implementations with readers that cannot read @code{@@} as a symbol.
;; (Actually, RScheme can now read @code{@@}, which leaves Stalin as the only
;; one the author knows of, so we'll probably go back to just using literal
;; @code{@@} symbols.
(define htmlprag-internal:at (string->symbol "@"))
;;; @section SHTML and SXML
;; TODO: Introduce SHTML.
;;; Some constants and a procedure are defined for convenience and portability
;;; when examining the SHTML produced by the tokenizer and parser.
;;; @defvar shtml-comment-symbol
;;; @defvarx shtml-decl-symbol
;;; @defvarx shtml-empty-symbol
;;; @defvarx shtml-end-symbol
;;; @defvarx shtml-entity-symbol
;;; @defvarx shtml-pi-symbol
;;; @defvarx shtml-start-symbol
;;; @defvarx shtml-text-symbol
;;; @defvarx shtml-top-symbol
;;;
;;; These variables are bound to the following case-sensitive symbols used in
;;; SHTML, respectively: @code{*COMMENT*}, @code{*DECL*}, @code{*EMPTY*},
;;; @code{*END*}, @code{*ENTITY*}, @code{*PI*}, @code{*START*}, @code{*TEXT*},
;;; and @code{*TOP*}. These can be used in lieu of the literal symbols in
;;; programs read by a case-insensitive Scheme reader.
(define shtml-comment-symbol (string->symbol "*COMMENT*"))
(define shtml-decl-symbol (string->symbol "*DECL*"))
(define shtml-empty-symbol (string->symbol "*EMPTY*"))
(define shtml-end-symbol (string->symbol "*END*"))
(define shtml-entity-symbol (string->symbol "*ENTITY*"))
(define shtml-pi-symbol (string->symbol "*PI*"))
(define shtml-start-symbol (string->symbol "*START*"))
(define shtml-text-symbol (string->symbol "*TEXT*"))
(define shtml-top-symbol (string->symbol "*TOP*"))
;;; @defvar shtml-named-char-id
;;; @defvarx shtml-numeric-char-id
;;;
;;; These variables are bound to the SHTML entity public identifier strings
;;; for symbolic and numeric character entities. These strings are currently
;;; @code{"additional"} and @code{"additional-char"}, respectively, but are
;;; likely to change in a future version of HtmlPrag, so programs should use
;;; the bindings rather than the literal strings directly.
(define shtml-named-char-id "additional")
(define shtml-numeric-char-id "additional-char")
;; TODO: Make public procedures for creating character entities, since the
;; current SHTML syntax for them is pretty nasty.
;;; @defproc shtml-entity-value entity
;;;
;;; Yields the value for the SHTML entity. Values of named entities are
;;; symbols, and values of numeric entities are numbers. For example:
;;;
;;; @lisp
;;; (define (f s) (shtml-entity-value (car (cdr (html->shtml s)))))
;;; (f " ") @result{} nbsp
;;; (f "ߐ") @result{} 2000
;;; @end lisp
(define (shtml-entity-value entity)
(if (and (list? entity)
(= (length entity) 3)
(eqv? (car entity) shtml-entity-symbol))
(let ((public-id (list-ref entity 1))
(system-id (list-ref entity 2)))
(cond ((equal? public-id shtml-named-char-id)
(string->symbol system-id))
((equal? public-id shtml-numeric-char-id)
(string->number system-id))
(else (htmlprag-internal:error "shtml-entity-value"
"invalid entity public id"
public-id))))
(htmlprag-internal:error "shtml-entity-value"
"not an entity"
entity)))
;;; @section Tokenizing
;;; The tokenizer is used by the higher-level structural parser, but can also
;;; be called directly for debugging purposes or unusual applications. Some of
;;; the list structure of tokens, such as for start tag tokens, is mutated and
;;; incorporated into the SHTML list structure emitted by the parser.
;; TODO: Document the token format.
;;; @defproc make-html-tokenizer in normalized?
;;;
;;; Constructs an HTML tokenizer procedure on input port @var{in}. If boolean
;;; @var{normalized?} is true, then tokens will be in a format conducive to use
;;; with a parser emitting normalized SXML. Each call to the resulting
;;; procedure yields a successive token from the input. When the tokens have
;;; been exhausted, the procedure returns the null list. For example:
;;;
;;; @lisp
;;; (define input (open-input-string "bar"))
;;; (define next (make-html-tokenizer input #f))
;;; (next) @result{} (a (@@ (href "foo")))
;;; (next) @result{} "bar"
;;; (next) @result{} (*END* a)
;;; (next) @result{} ()
;;; (next) @result{} ()
;;; @end lisp
(define make-html-tokenizer
;; TODO: Have the tokenizer replace contiguous whitespace within individual
;; text tokens with single space characters (except for when in `pre'
;; and verbatim elements). The parser will introduce new contiguous
;; whitespace (e.g., when text tokens are concatenated, invalid end
;; tags are removed, whitespace is irrelevant between certain
;; elements), but then the parser only has to worry about the first and
;; last character of each string. Perhaps the text tokens should have
;; both leading and trailing whitespace stripped, and contain flags for
;; whether or not leading and trailing whitespace occurred.
(letrec ((no-token '())
;; TODO: Maybe make this an option.
(verbatim-to-eof-elems '(plaintext))
;; TODO: Implement proper parsing of `verbatim-pair-elems' elements.
;; Note that we must support invalid termination like this:
(verbatim-pair-elems '(script server style xmp))
(ws-chars (list #\space
(htmlprag-internal:a2c 9)
(htmlprag-internal:a2c 10)
(htmlprag-internal:a2c 11)
(htmlprag-internal:a2c 12)
(htmlprag-internal:a2c 13)))
(output-string->string-or-false
(lambda (os)
(let ((s (htmlprag-internal:gosc os)))
(if (string=? s "") #f s))))
(output-string->symbol-or-false
(lambda (os)
(let ((s (output-string->string-or-false os)))
(if s (string->symbol s) #f))))
)
(lambda (in normalized?)
;; TODO: Make a tokenizer option that causes XML namespace qualifiers to
;; be ignored.
(letrec
(
;; Port buffer with inexpensive unread of one character and slightly
;; more expensive pushback of second character to unread. The
;; procedures themselves do no consing. The tokenizer currently
;; needs two-symbol lookahead, due to ambiguous "/" while parsing
;; element and attribute names, which could be either empty-tag
;; syntax or XML qualified names.
(c #f)
(next-c #f)
(c-consumed? #t)
(read-c (lambda ()
(if c-consumed?
(if next-c
(begin (set! c next-c)
(set! next-c #f))
(set! c (read-char in)))
(set! c-consumed? #t))))
(unread-c (lambda ()
(if c-consumed?
(set! c-consumed? #f)
;; TODO: Procedure name in error message really
;; isn't "make-html-tokenizer"...
(htmlprag-internal:error "make-html-tokenizer"
"already unread"
c))))
(push-c (lambda (new-c)
(if c-consumed?
(begin (set! c new-c)
(set! c-consumed? #f))
(if next-c
(htmlprag-internal:error
"make-html-tokenizer"
"pushback full"
c)
(begin (set! next-c c)
(set! c new-c)
(set! c-consumed? #f))))))
;; TODO: These procedures are a temporary convenience for
;; enumerating the pertinent character classes, with an eye
;; towards removing redundant tests of character class. These
;; procedures should be eliminated in a future version.
(c-eof? (lambda () (eof-object? c)))
(c-amp? (lambda () (eqv? c #\&)))
(c-apos? (lambda () (eqv? c #\')))
(c-bang? (lambda () (eqv? c #\!)))
(c-colon? (lambda () (eqv? c #\:)))
(c-quot? (lambda () (eqv? c #\")))
(c-equals? (lambda () (eqv? c #\=)))
(c-gt? (lambda () (eqv? c #\>)))
(c-lt? (lambda () (eqv? c #\<)))
(c-minus? (lambda () (eqv? c #\-)))
(c-pound? (lambda () (eqv? c #\#)))
(c-ques? (lambda () (eqv? c #\?)))
(c-semi? (lambda () (eqv? c #\;)))
(c-slash? (lambda () (eqv? c #\/)))
(c-splat? (lambda () (eqv? c #\*)))
(c-lf? (lambda () (eqv? c #\newline)))
(c-angle? (lambda () (memv c '(#\< #\>))))
(c-ws? (lambda () (memv c ws-chars)))
(c-alpha? (lambda () (char-alphabetic? c)))
(c-digit? (lambda () (char-numeric? c)))
(c-alphanum? (lambda () (or (c-alpha?) (c-digit?))))
(c-hexlet? (lambda () (memv c '(#\a #\b #\c #\d #\e #\f
#\A #\B #\C #\D #\E #\F))))
(skip-ws (lambda () (read-c) (if (c-ws?) (skip-ws) (unread-c))))
(make-start-token
(if normalized?
(lambda (name ns attrs)
(list name (cons htmlprag-internal:at attrs)))
(lambda (name ns attrs)
(if (null? attrs)
(list name)
(list name (cons htmlprag-internal:at attrs))))))
(make-empty-token
(lambda (name ns attrs)
(cons shtml-empty-symbol
(make-start-token name ns attrs))))
(make-end-token
(if normalized?
(lambda (name ns attrs)
(list shtml-end-symbol
name
(cons htmlprag-internal:at attrs)))
(lambda (name ns attrs)
(if (null? attrs)
(list shtml-end-symbol name)
(list shtml-end-symbol
name
(cons htmlprag-internal:at attrs))))))
(make-named-char-token
(lambda (name-str)
(list shtml-entity-symbol
shtml-named-char-id
name-str)))
(make-numeric-char-token
(lambda (number)
(list shtml-entity-symbol
shtml-numeric-char-id
(number->string number))))
(make-comment-token
(lambda (str) (list shtml-comment-symbol str)))
(make-decl-token
(lambda (parts) (cons shtml-decl-symbol parts)))
(scan-qname
;; TODO: Make sure we don't accept local names that have "*", since
;; this can break SXML tools. Have to validate this
;; afterwards if "verbatim-safe?". Also check for "@" and
;; maybe "@@". Check qname parsing code, especially for
;; verbatim mode. This is important!
(lambda (verbatim-safe?)
;; Note: If we accept some invalid local names, we only need two
;; symbols of lookahead to determine the end of a qname.
(letrec ((os #f)
(ns '())
(vcolons 0)
(good-os (lambda ()
(or os
(begin (set! os (open-output-string))
os)))))
(let loop ()
(read-c)
(cond ((c-eof?) #f)
((or (c-ws?) (c-splat?))
(if verbatim-safe?
(unread-c)))
((or (c-angle?) (c-equals?) (c-quot?) (c-apos?))
(unread-c))
((c-colon?)
(or (null? ns)
(set! ns (cons ":" ns)))
(if os
(begin
(set! ns (cons (htmlprag-internal:gosc os)
ns))
(set! os #f)))
(loop))
((c-slash?)
(read-c)
(cond ((or (c-eof?)
(c-ws?)
(c-equals?)
(c-apos?)
(c-quot?)
(c-angle?)
(c-splat?))
(unread-c)
(push-c #\/))
(else (write-char #\/ (good-os))
(write-char c os)
(loop))))
(else (write-char c (good-os))
(loop))))
(let ((ns (if (null? ns)
#f
(apply string-append
(htmlprag-internal:reverse!ok ns))))
(local (if os (htmlprag-internal:gosc os) #f)))
(if verbatim-safe?
;; TODO: Make sure we don't have ambiguous ":" or drop
;; any characters!
(cons ns local)
;; Note: We represent "xmlns:" syntax as normal qnames,
;; for lack of something better to do with them when we
;; don't support XML namespaces.
;;
;; TODO: Local names are currently forced to lowercase,
;; since HTML is usually case-insensitive. If XML
;; namespaces are used, we might wish to keep local
;; names case-sensitive.
(if local
(if ns
(if (string=? ns "xmlns")
(string->symbol (string-append ns ":" local))
(cons ns
(string->symbol
(htmlprag-internal:down!ok
local))))
(string->symbol
(htmlprag-internal:down!ok local)))
(if ns
(string->symbol
(htmlprag-internal:down!ok ns))
;; TODO: Ensure that it's OK to return #f as a
;; name.
#f)))))))
(scan-tag
(lambda (start?)
(skip-ws)
(let ((tag-name (scan-qname #f))
(tag-ns #f)
(tag-attrs #f)
(tag-empty? #f))
;; Scan element name.
(if (pair? tag-name)
(begin (set! tag-ns (car tag-name))
(set! tag-name (cdr tag-name))))
;; TODO: Ensure there's no case in which a #f tag-name isn't
;; compensated for later.
;;
;; Scan element attributes.
(set! tag-attrs
(let scan-attr-list ()
(read-c)
(cond ((c-eof?) '())
((c-angle?) (unread-c) '())
((c-slash?)
(set! tag-empty? #t)
(scan-attr-list))
((c-alpha?)
(unread-c)
(let ((attr (scan-attr)))
(cons attr (scan-attr-list))))
(else (scan-attr-list)))))
;; Find ">" or unnatural end.
(let loop ()
(read-c)
(cond ((c-eof?) no-token)
((c-slash?) (set! tag-empty? #t) (loop))
((c-gt?) #f)
((c-ws?) (loop))
(else (unread-c))))
;; Change the tokenizer mode if necessary.
(cond ((not start?) #f)
(tag-empty? #f)
;; TODO: Maybe make one alist lookup here, instead of
;; two.
((memq tag-name verbatim-to-eof-elems)
(set! nexttok verbeof-nexttok))
((memq tag-name verbatim-pair-elems)
(set! nexttok (make-verbpair-nexttok tag-name))))
;; Return a token object.
(if start?
(if tag-empty?
(make-empty-token tag-name tag-ns tag-attrs)
(make-start-token tag-name tag-ns tag-attrs))
(make-end-token tag-name tag-ns tag-attrs)))))
(scan-attr
(lambda ()
(let ((name (scan-qname #f))
(val #f))
(if (pair? name)
(set! name (cdr name)))
(let loop-equals-or-end ()
(read-c)
(cond ((c-eof?) no-token)
((c-ws?) (loop-equals-or-end))
((c-equals?)
(let loop-quote-or-unquoted ()
(read-c)
(cond ((c-eof?) no-token)
((c-ws?) (loop-quote-or-unquoted))
((or (c-apos?) (c-quot?))
(let ((term c))
(set! val (open-output-string))
(let loop-quoted-val ()
(read-c)
(cond ((c-eof?) #f)
((eqv? c term) #f)
(else (write-char c val)
(loop-quoted-val))))))
((c-angle?) (unread-c))
(else
(set! val (open-output-string))
(write-char c val)
(let loop-unquoted-val ()
(read-c)
(cond ((c-eof?) no-token)
((c-apos?) #f)
((c-quot?) #f)
((or (c-ws?) (c-angle?) (c-slash?))
(unread-c))
(else (write-char c val)
(loop-unquoted-val))))))))
(else (unread-c))))
(if normalized?
(list name (if val
(htmlprag-internal:gosc val)
(symbol->string name)))
(if val
(list name (htmlprag-internal:gosc val))
(list name))))))
(scan-comment
;; TODO: Rewrite this to use tail recursion rather than a state
;; variable.
(lambda ()
(let ((os (open-output-string))
(state 'start-minus))
(let loop ()
(read-c)
(cond ((c-eof?) #f)
((c-minus?)
(set! state
(case state
((start-minus) 'start-minus-minus)
((start-minus-minus body) 'end-minus)
((end-minus) 'end-minus-minus)
((end-minus-minus)
(write-char #\- os)
state)
(else (htmlprag-internal:error
"make-html-tokenizer"
"invalid state"
state))))
(loop))
((and (c-gt?) (eq? state 'end-minus-minus)) #f)
(else (case state
((end-minus) (write-char #\- os))
((end-minus-minus) (display "--" os)))
(set! state 'body)
(write-char c os)
(loop))))
(make-comment-token (htmlprag-internal:gosc os)))))
(scan-pi
(lambda ()
(skip-ws)
(let ((name (open-output-string))
(val (open-output-string)))
(let scan-name ()
(read-c)
(cond ((c-eof?) #f)
((c-ws?) #f)
((c-alpha?) (write-char c name) (scan-name))
(else (unread-c))))
;; TODO: Do we really want to emit #f for PI name?
(set! name (output-string->symbol-or-false name))
(let scan-val ()
(read-c)
(cond ((c-eof?) #f)
;; ((c-amp?) (display (scan-entity) val)
;; (scan-val))
((c-ques?)
(read-c)
(cond ((c-eof?) (write-char #\? val))
((c-gt?) #f)
(else (write-char #\? val)
(unread-c)
(scan-val))))
(else (write-char c val) (scan-val))))
(list shtml-pi-symbol
name
(htmlprag-internal:gosc val)))))
(scan-decl
;; TODO: Find if SXML includes declaration forms, and if so,
;; use whatever format SXML wants.
;;
;; TODO: Rewrite to eliminate state variables.
(letrec
((scan-parts
(lambda ()
(let ((part (open-output-string))
(nonsymbol? #f)
(state 'before)
(last? #f))
(let loop ()
(read-c)
(cond ((c-eof?) #f)
((c-ws?)
(case state
((before) (loop))
((quoted) (write-char c part) (loop))))
((and (c-gt?) (not (eq? state 'quoted)))
(set! last? #t))
((and (c-lt?) (not (eq? state 'quoted)))
(unread-c))
((c-quot?)
(case state
((before) (set! state 'quoted) (loop))
((unquoted) (unread-c))
((quoted) #f)))
(else
(if (eq? state 'before)
(set! state 'unquoted))
(set! nonsymbol? (or nonsymbol?
(not (c-alphanum?))))
(write-char c part)
(loop))))
(set! part (htmlprag-internal:gosc part))
(if (string=? part "")
'()
(cons (if (or (eq? state 'quoted) nonsymbol?)
part
;; TODO: Normalize case of things we make
;; into symbols here.
(string->symbol part))
(if last?
'()
(scan-parts))))))))
(lambda () (make-decl-token (scan-parts)))))
(scan-entity
(lambda ()
(read-c)
(cond ((c-eof?) "&")
((c-alpha?)
;; TODO: Do entity names have a maximum length?
(let ((name (open-output-string)))
(write-char c name)
(let loop ()
(read-c)
(cond ((c-eof?) #f)
((c-alpha?) (write-char c name) (loop))
((c-semi?) #f)
(else (unread-c))))
(set! name (htmlprag-internal:gosc name))
;; TODO: Make the entity map an option.
(let ((pair (assoc name '(("amp" . "&")
("apos" . "'")
("gt" . ">")
("lt" . "<")
("quot" . "\"")))))
(if pair
(cdr pair)
(make-named-char-token name)))))
((c-pound?)
(let ((num (open-output-string))
(hex? #f))
(read-c)
(cond ((c-eof?) #f)
((memv c '(#\x #\X)) (set! hex? #t) (read-c)))
(let loop ()
(cond ((c-eof?) #f)
((c-semi?) #f)
((or (c-digit?) (and hex? (c-hexlet?)))
(write-char c num)
(read-c)
(loop))
(else (unread-c))))
(set! num (htmlprag-internal:gosc num))
(if (string=? num "")
""
(let ((n (string->number num (if hex? 16 10))))
(if (and (<= 32 n 255) (not (= n 127)))
(string (htmlprag-internal:a2c n))
(make-numeric-char-token n))))))
(else (unread-c) "&"))))
(normal-nexttok
(lambda ()
(read-c)
(cond ((c-eof?) no-token)
((c-lt?)
(let loop ()
(read-c)
(cond ((c-eof?) "<")
((c-ws?) (loop))
((c-slash?) (scan-tag #f))
((c-ques?) (scan-pi))
((c-bang?) (let loop ()
(read-c)
(cond ((c-eof?) no-token)
((c-ws?) (loop))
((c-minus?) (scan-comment))
(else (unread-c)
(scan-decl)))))
((c-alpha?) (unread-c) (scan-tag #t))
(else (unread-c) "<"))))
((c-gt?) ">")
(else (let ((os (open-output-string)))
(let loop ()
(cond ((c-eof?) #f)
((c-angle?) (unread-c))
((c-amp?)
(let ((entity (scan-entity)))
(if (string? entity)
(begin (display entity os)
(read-c)
(loop))
(let ((saved-nexttok nexttok))
(set! nexttok
(lambda ()
(set! nexttok
saved-nexttok)
entity))))))
(else (write-char c os)
(or (c-lf?)
(begin (read-c) (loop))))))
(let ((text (htmlprag-internal:gosc os)))
(if (equal? text "")
(nexttok)
text)))))))
(verbeof-nexttok
(lambda ()
(read-c)
(if (c-eof?)
no-token
(let ((os (open-output-string)))
(let loop ()
(or (c-eof?)
(begin (write-char c os)
(or (c-lf?)
(begin (read-c) (loop))))))
(htmlprag-internal:gosc os)))))
(make-verbpair-nexttok
(lambda (elem-name)
(lambda ()
(let ((os (open-output-string)))
;; Accumulate up to a newline-terminated line.
(let loop ()
(read-c)
(cond ((c-eof?)
;; Got EOF in verbatim context, so set the normal
;; nextok procedure, then fall out of loop.
(set! nexttok normal-nexttok))
((c-lt?)
;; Got "<" in verbatim context, so get next
;; character.
(read-c)
(cond ((c-eof?)
;; Got "<" then EOF, so set to the normal
;; nexttok procedure, add the "<" to the
;; verbatim string, and fall out of loop.
(set! nexttok normal-nexttok)
(write-char #\< os))
((c-slash?)
;; Got "", so...
(read-c)
(cond
((c-eof?)
(display "" os))
((c-alpha?)
;; Got "" followed by alpha, so unread
;; the alpha, scan qname, compare...
(unread-c)
(let* ((vqname (scan-qname #t))
(ns (car vqname))
(local (cdr vqname)))
;; Note: We ignore XML namespace
;; qualifier for purposes of comparison.
;;
;; Note: We're interning strings here for
;; comparison when in theory there could
;; be many such unique interned strings
;; in a valid HTML document, although in
;; practice this should not be a problem.
(if (and local
(eqv? (string->symbol
(htmlprag-internal:down
local))
elem-name))
;; This is the terminator tag, so
;; scan to the end of it, set the
;; nexttok, and fall out of the loop.
(begin
(let scan-to-end ()
(read-c)
(cond ((c-eof?) #f)
((c-gt?) #f)
((c-lt?) (unread-c))
((c-alpha?)
(unread-c)
;; Note: This is an
;; expensive way to skip
;; over an attribute, but
;; in practice more
;; verbatim end tags will
;; not have attributes.
(scan-attr)
(scan-to-end))
(else (scan-to-end))))
(set! nexttok
(lambda ()
(set! nexttok
normal-nexttok)
(make-end-token
elem-name #f '()))))
;; This isn't the terminator tag, so
;; add to the verbatim string the
;; "" and the characters of what we
;; were scanning as a qname, and
;; recurse in the loop.
(begin
(display "" os)
(if ns
(begin (display ns os)
(display ":" os)))
(if local
(display local os))
(loop)))))
(else
;; Got "" and non-alpha, so unread new
;; character, add the "" to verbatim
;; string, then loop.
(unread-c)
(display "" os)
(loop))))
(else
;; Got "<" and non-slash, so unread the new
;; character, write the "<" to the verbatim
;; string, then loop.
(unread-c)
(write-char #\< os)
(loop))))
(else
;; Got non-"<" in verbatim context, so just add it
;; to the buffer, then, if it's not a linefeed, fall
;; out of the loop so that the token can be
;; returned.
(write-char c os)
(or (c-lf?) (loop)))))
;; Return the accumulated line string, if non-null, or call
;; nexttok.
(or (output-string->string-or-false os) (nexttok))))))
(nexttok #f))
(set! nexttok normal-nexttok)
(lambda () (nexttok))))))
;;; @defproc tokenize-html in normalized?
;;;
;;; Returns a list of tokens from input port @var{in}, normalizing according to
;;; boolean @var{normalized?}. This is probably most useful as a debugging
;;; convenience. For example:
;;;
;;; @lisp
;;; (tokenize-html (open-input-string "bar") #f)
;;; @result{} ((a (@@ (href "foo"))) "bar" (*END* a))
;;; @end lisp
(define (tokenize-html in normalized?)
(let ((next-tok (make-html-tokenizer in normalized?)))
(let loop ((tok (next-tok)))
(if (null? tok)
'()
(cons tok (loop (next-tok)))))))
;;; @defproc shtml-token-kind token
;;;
;;; Returns a symbol indicating the kind of tokenizer @var{token}:
;;; @code{*COMMENT*}, @code{*DECL*}, @code{*EMPTY*}, @code{*END*},
;;; @code{*ENTITY*}, @code{*PI*}, @code{*START*}, @code{*TEXT*}.
;;; This is used by higher-level parsing code. For example:
;;;
;;; @lisp
;;; (map shtml-token-kind
;;; (tokenize-html (open-input-string ">shtml} rather than calling the tokenizer directly.
;; @defvar htmlprag-internal:empty-elements
;;
;; List of names of HTML element types that have no content, represented as a
;; list of symbols. This is used internally by the parser and encoder. The
;; effect of mutating this list is undefined.
;; TODO: Document exactly which elements these are, after we make the new
;; parameterized parser constructor.
(define htmlprag-internal:empty-elements
'(area base br frame hr img input isindex keygen link meta object param
spacer wbr))
;;; @defproc parse-html/tokenizer tokenizer normalized?
;;;
;;; Emits a parse tree like @code{html->shtml} and related procedures, except
;;; using @var{tokenizer} as a source of tokens, rather than tokenizing from an
;;; input port. This procedure is used internally, and generally should not be
;;; called directly.
(define parse-html/tokenizer
;; TODO: Document the algorithm, then see if rewriting as idiomatic Scheme
;; can make it more clear.
(letrec ((empty-elements
;; TODO: Maybe make this an option. This might also be an
;; acceptable way to parse old HTML that uses the `p' element
;; as a paragraph terminator.
htmlprag-internal:empty-elements)
(parent-constraints
;; TODO: Maybe make this an option.
'((area . (map))
(body . (html))
(caption . (table))
(colgroup . (table))
(dd . (dl))
(dt . (dl))
(frame . (frameset))
(head . (html))
(isindex . (head))
(li . (dir menu ol ul))
(meta . (head))
(noframes . (frameset))
(option . (select))
(p . (body td th))
(param . (applet))
(tbody . (table))
(td . (tr))
(th . (tr))
(thead . (table))
(title . (head))
(tr . (table tbody thead))))
(start-tag-name (lambda (tag-token) (car tag-token)))
(end-tag-name (lambda (tag-token) (list-ref tag-token 1))))
(lambda (tokenizer normalized?)
;; Example `begs' value:
;;
;; ( ((head ...) . ( (title ...) ))
;; ((html ...) . ( (head ...) (*COMMENT* ...) ))
;; (#f . ( (html ...) (*DECL* doctype ...) )) )
(let ((begs (list (cons #f '()))))
(letrec ((add-to-current-beg
(lambda (tok)
(set-cdr! (car begs) (cons tok (cdr (car begs))))))
(finish-all-begs
(lambda ()
(let ((toplist #f))
(map (lambda (beg) (set! toplist (finish-beg beg)))
begs)
toplist)))
(finish-beg
(lambda (beg)
(let ((start-tok (car beg)))
(if start-tok
(htmlprag-internal:append!
(car beg)
(htmlprag-internal:reverse!ok (cdr beg)))
(htmlprag-internal:reverse!ok (cdr beg))))))
(finish-begs-to
(lambda (name lst)
(let* ((top (car lst))
(starttag (car top)))
(cond ((not starttag) #f)
((eqv? name (start-tag-name starttag))
(set! begs (cdr lst))
(finish-beg top)
#t)
(else (if (finish-begs-to name (cdr lst))
(begin (finish-beg top) #t)
#f))))))
(finish-begs-upto
(lambda (parents lst)
(let* ((top (car lst))
(starttag (car top)))
(cond ((not starttag) #f)
((memq (start-tag-name starttag) parents)
(set! begs lst)
#t)
(else (if (finish-begs-upto parents (cdr lst))
(begin (finish-beg top) #t)
#f)))))))
(let loop ()
(let ((tok (tokenizer)))
(if (null? tok)
(finish-all-begs)
(let ((kind (shtml-token-kind tok)))
(cond ((memv kind `(,shtml-comment-symbol
,shtml-decl-symbol
,shtml-entity-symbol
,shtml-pi-symbol
,shtml-text-symbol))
(add-to-current-beg tok))
((eqv? kind shtml-start-symbol)
(let* ((name (start-tag-name tok))
(cell (assq name parent-constraints)))
(and cell (finish-begs-upto (cdr cell) begs))
(add-to-current-beg tok)
(or (memq name empty-elements)
(set! begs (cons (cons tok '()) begs)))))
((eqv? kind shtml-empty-symbol)
;; Empty tag token, so just add it to current
;; beginning while stripping off leading `*EMPTY*'
;; symbol so that the token becomes normal SXML
;; element syntax.
(add-to-current-beg (cdr tok)))
((eqv? kind shtml-end-symbol)
(let ((name (end-tag-name tok)))
(if name
;; Try to finish to a start tag matching this
;; end tag. If none, just drop the token,
;; though we used to add it to the current
;; beginning.
(finish-begs-to name begs)
;; We have an anonymous end tag, so match it
;; with the most recent beginning. If no
;; beginning to match, then just drop the
;; token, though we used to add it to the
;; current beginning.
(and (car (car begs))
(begin (finish-beg (car begs))
(set! begs (cdr begs)))))))
(else (htmlprag-internal:error "parse-html/tokenizer"
"unknown tag kind"
kind)))
(loop))))))))))
;; @defproc htmlprag-internal:parse-html input normalized? top?
;;
;; This procedure is now used internally by @code{html->shtml} and its
;; variants, and should not be used directly by programs. The interface is
;; likely to change in future versions of HtmlPrag.
(define (htmlprag-internal:parse-html input normalized? top?)
(let ((parse
(lambda ()
(parse-html/tokenizer
(make-html-tokenizer
(cond ((input-port? input) input)
((string? input) (open-input-string input))
(else (htmlprag-internal:error
"htmlprag-internal:parse-html"
"invalid input type"
input)))
normalized?)
normalized?))))
(if top?
(cons shtml-top-symbol (parse))
(parse))))
;;; @defproc html->sxml-0nf input
;;; @defprocx html->sxml-1nf input
;;; @defprocx html->sxml-2nf input
;;; @defprocx html->sxml input
;;;
;;; Permissively parse HTML from @var{input}, which is either an input port or
;;; a string, and emit an SHTML equivalent or approximation. To borrow and
;;; slightly modify an example from [SSAX-HTML]:
;;;
;;; @lisp
;;; (html->shtml
;;; "whatever
;;; link
;;; BLah italic bold ened
;;; still < bold
But not done yet...")
;;; @result{}
;;; (*TOP* (html (head (title) (title "whatever"))
;;; (body "\n"
;;; (a (@@ (href "url")) "link")
;;; (p (@@ (align "center"))
;;; (ul (@@ (compact) (style "aa")) "\n"))
;;; (p "BLah"
;;; (*COMMENT* " comment ")
;;; " "
;;; (i " italic " (b " bold " (tt " ened")))
;;; "\n"
;;; "still < bold "))
;;; (p " But not done yet...")))
;;; @end lisp
;;;
;;; Note that in the emitted SHTML the text token @code{"still < bold"} is
;;; @emph{not} inside the @code{b} element, which represents an unfortunate
;;; failure to emulate all the quirks-handling behavior of some popular Web
;;; browsers.
;;;
;;; The procedures @code{html->sxml-@var{n}nf} for @var{n} 0 through 2
;;; correspond to 0th through 2nd normal forms of SXML as specified in [SXML],
;;; and indicate the minimal requirements of the emitted SXML.
;;;
;;; @code{html->sxml} and @code{html->shtml} are currently aliases for
;;; @code{html->sxml-0nf}, and can be used in scripts and interactively, when
;;; terseness is important and any normal form of SXML would suffice.
(define (html->sxml-0nf input) (htmlprag-internal:parse-html input #f #t))
(define (html->sxml-1nf input) (htmlprag-internal:parse-html input #f #t))
(define (html->sxml-2nf input) (htmlprag-internal:parse-html input #t #t))
(define html->sxml html->sxml-0nf)
(define html->shtml html->sxml-0nf)
;;; @section HTML Encoding
;;; Two procedures encode the SHTML representation as conventional HTML,
;;; @code{write-shtml-as-html} and @code{shtml->html}. These are perhaps most
;;; useful for emitting the result of parsed and transformed input HTML. They
;;; can also be used for emitting HTML from generated or handwritten SHTML.
;;; @defproc write-shtml-as-html shtml out
;;;
;;; Writes a conventional HTML transliteration of the SHTML @var{shtml} to
;;; output port @var{out}. HTML elements of types that are always empty are
;;; written using HTML4-compatible XHTML tag syntax. No inter-tag whitespace
;;; or line breaks not explicit in @var{shtml} is emitted. The @var{shtml}
;;; should normally include a newline at the end of the document. For example
;;; (which might not work verbatim in all Scheme implementations):
;;;
;;; @lisp
;;; (write-shtml-as-html
;;; '((html (head (title "My Title"))
;;; (body (@@ (bgcolor "white"))
;;; (h1 "My Heading")
;;; (p "This is a paragraph.")
;;; (p "This is another paragraph."))))
;;; (current-output-port))
;;; @print{} My TitleMy Heading
This is a paragraph.
This is
;;; @print{} another paragraph.
;;; @end lisp
(define (write-shtml-as-html shtml out)
(letrec
((write-shtml-text
(lambda (str out)
(let ((len (string-length str)))
(let loop ((i 0))
(if (< i len)
(begin (display (let ((c (string-ref str i)))
(case c
;; ((#\") """)
((#\&) "&")
((#\<) "<")
((#\>) ">")
(else c)))
out)
(loop (+ 1 i))))))))
(write-dquote-ampified
(lambda (str out)
;; TODO: If we emit """, we really should parse it, and HTML
;; 4.01 says we should, but anachronisms in HTML create the
;; potential for nasty mutilation of URI in attribute values.
(let ((len (string-length str)))
(let loop ((i 0))
(if (< i len)
(begin (display (let ((c (string-ref str i)))
(if (eqv? c #\") """ c))
out)
(loop (+ 1 i))))))))
(do-thing
(lambda (thing)
(cond ((string? thing) (write-shtml-text thing out))
((list? thing) (if (not (null? thing))
(do-list-thing thing)))
(else (htmlprag-internal:error "write-shtml-as-html"
"invalid SHTML thing"
thing)))))
(do-list-thing
(lambda (thing)
(let ((head (car thing)))
(cond ((symbol? head)
;; Head is a symbol, so...
(cond ((eq? head shtml-comment-symbol)
;; TODO: Make sure the comment text doesn't contain a
;; comment end sequence.
(display "" out))
((eq? head shtml-decl-symbol)
(let ((head (car (cdr thing))))
(display "string head) out)
(for-each
(lambda (n)
(cond ((symbol? n)
(display #\space out)
(display (symbol->string n) out))
((string? n)
(display " \"" out)
(write-dquote-ampified n out)
(display #\" out))
(else (htmlprag-internal:error
"write-shtml-as-html"
"invalid SHTML decl"
thing))))
(cdr (cdr thing)))
(display #\> out)))
((eq? head shtml-entity-symbol)
(let ((val (shtml-entity-value thing)))
(display #\& out)
(if (integer? val)
(display #\# out))
(display val out))
(display #\; out))
((eq? head shtml-pi-symbol)
(display "" out)
(display (symbol->string (car (cdr thing))) out)
(display #\space out)
(display (car (cdr (cdr thing))) out)
;; TODO: Error-check that no more rest of PI.
(display "?>" out))
((eq? head shtml-top-symbol)
(for-each do-thing (cdr thing)))
((eq? head shtml-empty-symbol)
#f)
((memq head `(,shtml-end-symbol
,shtml-start-symbol
,shtml-text-symbol))
(htmlprag-internal:error "write-shtml-as-html"
"invalid SHTML symbol"
head))
((eq? head htmlprag-internal:at)
(htmlprag-internal:error
"write-shtml-as-html"
"illegal position of SHTML attributes"
thing))
(else
(display #\< out)
(display head out)
(let* ((rest (cdr thing)))
(if (not (null? rest))
(let ((second (car rest)))
(and (list? second)
(not (null? second))
(eq? (car second) htmlprag-internal:at)
(begin (for-each do-attr (cdr second))
(set! rest (cdr rest))))))
(if (memq head
htmlprag-internal:empty-elements)
;; TODO: Error-check to make sure the element
;; has no content other than attributes.
;; We have to test for cases like:
;; (br (@) () (()))
(display " />" out)
(begin (display #\> out)
(for-each do-thing rest)
(display "" out)
(display (symbol->string head) out)
(display #\> out)))))))
((or (list? head) (string? head))
;; Head is a list or string, which might occur as the result
;; of an SXML transform, so we'll cope.
(for-each do-thing thing))
(else
;; Head is NOT a symbol, list, or string, so error.
(htmlprag-internal:error "write-shtml-as-html"
"invalid SHTML list"
thing))))))
(write-attr-val-dquoted
(lambda (str out)
(display #\" out)
(display str out)
(display #\" out)))
(write-attr-val-squoted
(lambda (str out)
(display #\' out)
(display str out)
(display #\' out)))
(write-attr-val-dquoted-and-amped
(lambda (str out)
(display #\" out)
(write-dquote-ampified str out)
(display #\" out)))
(write-attr-val
(lambda (str out)
(let ((len (string-length str)))
(let find-dquote-and-squote ((i 0))
(if (= i len)
(write-attr-val-dquoted str out)
(let ((c (string-ref str i)))
(cond ((eqv? c #\")
(let find-squote ((i (+ 1 i)))
(if (= i len)
(write-attr-val-squoted str out)
(if (eqv? (string-ref str i) #\')
(write-attr-val-dquoted-and-amped str out)
(find-squote (+ 1 i))))))
((eqv? c #\')
(let find-dquote ((i (+ 1 i)))
(if (= i len)
(write-attr-val-dquoted str out)
(if (eqv? (string-ref str i) #\")
(write-attr-val-dquoted-and-amped str out)
(find-dquote (+ 1 i))))))
(else (find-dquote-and-squote (+ 1 i))))))))))
(do-attr
(lambda (attr)
(or (list? attr)
(htmlprag-internal:error "write-shtml-as-html"
"invalid SHTML attribute"
attr))
(if (not (null? attr))
(let ((name (car attr)))
(or (symbol? name)
(htmlprag-internal:error "write-shtml-as-html"
"invalid name in SHTML attribute"
attr))
(if (not (eq? name htmlprag-internal:at))
(begin
(display #\space out)
(display name out)
(let ((rest (cdr attr)))
(or (list? rest)
(htmlprag-internal:error
"write-shtml-as-html"
"malformed SHTML attribute"
attr))
(if (not (null? rest))
(let ((value (car rest)))
(cond ((string? value)
(display #\= out)
(write-attr-val value out))
((eq? value #t)
;; Note: This is not valid SXML, but
;; perhaps should be.
#f)
(else
(htmlprag-internal:error
"write-shtml-as-html"
"invalid value in SHTML attribute"
attr)))))))))))))
(do-thing shtml)
(if #f #f)))
;;; @defproc shtml->html shtml
;;;
;;; Yields an HTML encoding of SHTML @var{shtml} as a string. For example:
;;;
;;; @lisp
;;; (shtml->html
;;; (html->shtml
;;; "This is
bold italic b > text.
"))
;;; @result{} "This is
bold italic text.
"
;;; @end lisp
;;;
;;; Note that, since this procedure constructs a string, it should normally
;;; only be used when the HTML is relatively small. When encoding HTML
;;; documents of conventional size and larger, @var{write-shtml-as-html} is
;;; much more efficient.
(define (shtml->html shtml)
(let ((os (open-output-string)))
(write-shtml-as-html shtml os)
(htmlprag-internal:gosc os)))
;;; @section Deprecated
;;; As HtmlPrag evolves towards version 1.0,
;;; The equivalences below show the deprecated expressions below, the code on
;;; the left is deprecated and should be replaced with the code on the right.
;;; @lisp
;;; sxml->html @equiv{} shtml->html
;;; write-sxml-html @equiv{} write-shtml-as-html
;;; @end lisp
(define sxml->html shtml->html)
(define write-sxml-html write-shtml-as-html)
;;; @section Tests
;;; A regression test suite is defined as procedure @code{test-htmlprag} in the
;;; source file. The test suite can be run under various Scheme
;;; implementations with Unix shell commands like:
;;;
;;; @itemize @
;;;
;;; @item Bigloo
;;; @example
;;; bigloo -eval '(load "htmlprag.scm") (test-htmlprag) (exit)'
;;; @end example
;;;
;;; @item Chicken
;;; @example
;;; csi -batch -eval '(load "htmlprag.scm") (test-htmlprag)'
;;; @end example
;;;
;;; @item Gauche
;;; @example
;;; gosh -l./htmlprag.scm -e"(begin (test-htmlprag) (exit))"
;;; @end example
;;;
;;; @item Guile
;;; @example
;;; guile -l htmlprag.scm -c "(test-htmlprag)"
;;; @end example
;;;
;;; @c @item Kawa
;;; @c @example
;;; @c kawa -f htmlprag.scm -e "(test-htmlprag)"
;;; @c @end example
;;;
;;; @item MIT Scheme
;;; @example
;;; mit-scheme <input-port)
;;; (define open-output-string make-accumulator-output-port)
;;; (define get-output-string get-output-from-accumulator)
;;; (load "htmlprag.scm") (test-htmlprag)
;;; EOH
;;; @end example
;;;
;;; @item PLT MzScheme
;;; @example
;;; mzscheme -qfe htmlprag.scm "(begin (test-htmlprag) (exit))"
;;; @end example
;;;
;;; @item RScheme
;;; @example
;;; rs -e '(load "htmlprag.scm") (test-htmlprag)' -exit
;;; @end example
;;;
;;; @item Scheme 48 @ (requires edit of @code{htmlprag-internal:a2c})
;;; @example
;;; scheme48 < ")
(write result)
(newline)
(if (equal? result expected)
(begin (set! passed (+ 1 passed))
(display ";; Passed.")
(newline))
(begin (set! failed (+ 1 failed))
(display ";; ***FAILED*** Expected:")
(newline)
(display ";; ")
(write expected)
(newline))))))
(t1 (lambda (input expected)
(test html->shtml
'html->shtml
(list input)
(cons shtml-top-symbol expected))))
(t2 (lambda (input expected)
(test shtml->html
'shtml->html
(list input)
expected)))
(at htmlprag-internal:at)
(comment shtml-comment-symbol)
(decl shtml-decl-symbol)
(entity shtml-entity-symbol)
(pi shtml-pi-symbol)
(lf (string (htmlprag-internal:a2c 10))))
(tests-begin)
(t1 ">" '((a ">")))
(t1 "" '((a "<" ">")))
(t1 "<>" '("<" ">"))
(t1 "< >" '("<" ">"))
(t1 "< a>" '((a)))
(t1 "< a / >" '((a)))
(t1 "" '(">" (a)))
(t1 ">" '())
(t1 "<\">" '("<" "\"" ">"))
(t1 (string-append "xxxaaa" lf "bbb" lf "cbbb" `("aaa" (,comment " xxx ") "bbb"))
(t1 "aaabbb" `("aaa" (,comment " xxx ") "bbb"))
(t1 "aaabbb" `("aaa" (,comment " xxx -") "bbb"))
(t1 "aaabbb" `("aaa" (,comment " xxx --") "bbb"))
(t1 "aaabbb" `("aaa" (,comment " xxx -y") "bbb"))
(t1 "aaabbb" `("aaa" (,comment "-") "bbb"))
(t1 "aaabbb" `("aaa" (,comment "") "bbb"))
(t1 "aaabbb" `("aaa" (,comment "->bbb")))
(t1 "
" '((hr)))
(t1 "
" '((hr)))
(t1 "
" '((hr)))
(t1 "
" `((hr (,at (noshade)))))
(t1 "
" `((hr (,at (noshade)))))
(t1 "
" `((hr (,at (noshade)))))
(t1 "
" `((hr (,at (noshade)))))
(t1 "
" `((hr (,at (noshade "1")))))
(t1 "
" `((hr (,at (noshade "1")))))
(t1 "aaabbb
ccc
ddd" '((q "aaa" (p) "bbb") "ccc" "ddd"))
(t1 "<" '("<"))
(t1 ">" '(">"))
(t1 "Gilbert & Sullivan" '("Gilbert & Sullivan"))
(t1 "Gilbert & Sullivan" '("Gilbert & Sullivan"))
(t1 "Gilbert & Sullivan" '("Gilbert & Sullivan"))
(t1 "Copyright © Foo" `("Copyright "
(,entity "additional" "copy")
" Foo"))
(t1 "aaa©bbb" `("aaa" (,entity "additional" "copy") "bbb"))
(t1 "aaa©" `("aaa" (,entity "additional" "copy")))
(t1 "*" '("*"))
(t1 "*" '("*"))
(t1 "*x" '("*x"))
(t1 "" (list (string (htmlprag-internal:a2c 151))))
(t1 "Ϩ" `((,entity "additional-char" "1000")))
(t1 "B" '("B"))
(t1 "¢" (list (string (htmlprag-internal:a2c 162))))
(t1 "ÿ" (list (string (htmlprag-internal:a2c 255))))
(t1 "Ā" `((,entity "additional-char" "256")))
(t1 "B" '("B"))
(t1 "&42;" '("&42;"))
(t1 "aaa©bbb&ccc<ddd&>eee*fffϨgggZhhh"
`("aaa"
(,entity "additional" "copy")
"bbb&ccceee*fff"
(,entity "additional-char" "1000")
"gggZhhh"))
(t1 (string-append
"
2")
`((img (,at
(src
"http://pics.ebay.com/aw/pics/listings/ebayLogo_38x16.gif")
(border "0") (width "38") (height "16")
(hspace "5") (vspace "0")))
"2"))
(t1 "eee" `((aaa (,at (bbb "ccc") (ddd)) "eee")))
(t1 "eee" `((aaa (,at (bbb "ccc") (ddd)) "eee")))
(t1 (string-append
"My TitleThis is a bold-italic test of "
"broken HTML.
Yes it is.")
`((html (head (title "My Title"))
(body (,at (bgcolor "white") (foo "42"))
"This is a "
(b (i "bold-italic"))
" test of "
"broken HTML."
(br)
"Yes it is."))))
(t1 (string-append
"")
`((,decl ,(string->symbol "DOCTYPE")
html
,(string->symbol "PUBLIC")
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")))
(t1 (string-append
"")
`((html (,at (xmlns "http://www.w3.org/1999/xhtml")
(lang "en") (lang "en")))))
(t1 (string-append
""
"Frobnostication"
"Moved to "
"here.")
`((html (,at (xmlns:html "http://www.w3.org/TR/REC-html40"))
(head (title "Frobnostication"))
(body (p "Moved to "
(a (,at (href "http://frob.com"))
"here."))))))
(t1 (string-append
""
"Layman, A"
"33B"
"Check Status"
"1997-05-24T07:55:00+1")
`((reservation (,at (,(string->symbol "xmlns:HTML")
"http://www.w3.org/TR/REC-html40"))
(name (,at (class "largeSansSerif"))
"Layman, A")
(seat (,at (class "Y") (class "largeMonotype"))
"33B")
(a (,at (href "/cgi-bin/ResStatus"))
"Check Status")
(departure "1997-05-24T07:55:00+1"))))
(t1 (string-append
"whatever"
"link"
"BLah italic bold ened "
" still < bold
But not done yet...")
`((html (head (title) (title "whatever"))
(body (a (,at (href "url")) "link")
(p (,at (align "center"))
(ul (,at (compact) (style "aa"))))
(p "BLah"
(,comment " comment ")
" "
(i " italic " (b " bold " (tt " ened ")))
" still < bold "))
(p " But not done yet..."))))
(t1 ""
`((,pi xml "version=\"1.0\" encoding=\"UTF-8\"")))
(t1 "" `((,pi php "php_info(); ")))
(t1 " blort ?>" `((,pi foo "bar ? baz > blort ")))
(t1 "x" `((,pi foo "b") "x"))
(t1 "x" `((,pi foo "") "x"))
(t1 "x" `((,pi foo "") "x"))
(t1 "x" `((,pi foo "") "x"))
(t1 "x" `((,pi f "") "x"))
(t1 "?>x" `((,pi #f "") "x"))
(t1 ">x" `((,pi #f ">x")))
(t1 "blort" `((foo (,at (bar "baz")) "blort")))
(t1 "blort" `((foo (,at (bar "baz")) "blort")))
(t1 "blort" `((foo (,at (bar "baz'>blort")))))
(t1 "c") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "a b>cd" '((xmp "a b>c") "d"))
(t1 "a b >cd" '((xmp "a b >c") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(t1 "acd" '((xmp "ac") "d"))
(let ((expected `((p "real1")
,lf
(xmp ,lf
,(string-append "alpha" lf)
,(string-append "fake
" lf)
,(string-append "bravo" lf))
(p "real2"))))
(t1 (string-append "real1
" lf
"" lf
"alpha" lf
"fake
" lf
"bravo" lf
"real2
")
expected)
(t1 (string-append "real1
" lf
"" lf
"alpha" lf
"fake
" lf
"bravo" lf
"real2
")
expected))
(t1 "ax" '((xmp "a") "x"))
(t1 (string-append "a" lf "x") `((xmp ,(string-append "a" lf))
"x"))
(t1 "x" '((xmp) "x"))
(t1 "aaaa" '((xmp "a")))
(t1 "a<" '((xmp "a<")))
(t1 "a" '((xmp "a")))
(t1 "" '((xmp)))
(t1 "xxx" '((script "xxx")))
(t1 "xxx" '((script) "xxx"))
;; TODO: Add verbatim-pair cases with attributes in the end tag.
(t2 '(p) "")
(t2 '(p "CONTENT") "CONTENT
")
(t2 '(br) "
")
(t2 '(br "CONTENT") "
")
(t2 `(hr (,at (clear "all"))) "
")
(t2 `(hr (,at (noshade))) "
")
(t2 `(hr (,at (noshade #t))) "
")
(t2 `(hr (,at (noshade "noshade"))) "
")
(t2 `(hr (,at (aaa "bbbccc"))) "
")
(t2 `(hr (,at (aaa "bbb'ccc"))) "
")
(t2 `(hr (,at (aaa "bbb\"ccc"))) "
")
(t2 `(hr (,at (aaa "bbb\"ccc'ddd"))) "
")
(t2 `(,pi xml "version=\"1.0\" encoding=\"UTF-8\"")
"")
(t2 `(,decl ,(string->symbol "DOCTYPE")
html
,(string->symbol "PUBLIC")
"-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")
(string-append
""))
;; TODO: Write more test cases for HTML encoding.
;; TODO: Document this.
;;
;; (define html-1 "")
;; (define shtml (html->shtml html-1))
;; shtml
;; (define html-2 (shtml->html shtml))
;; html-2
(tests-end)))
;;; @unnumberedsec History
;;; @table @asis
;;;
;;; @item Version 0.11 --- 13 May 2004
;;; To reduce likely namespace collisions with SXML tools, and in anticipation
;;; of a forthcoming set of new features, introduced the concept of ``SHTML,''
;;; which will be elaborated upon in a future version of HtmlPrag. Renamed
;;; @code{sxml-@var{x}-symbol} to @code{shtml-@var{x}-symbol},
;;; @code{sxml-html-@var{x}} to @code{shtml-@var{x}}, and
;;; @code{sxml-token-kind} to @code{shtml-token-kind}. @code{html->shtml},
;;; @code{shtml->html}, and @code{write-shtml-as-html} have been added as
;;; names. Considered deprecated but still defined (see the ``Deprecated''
;;; section of this documentation) are @code{sxml->html} and
;;; @code{write-sxml-html}. The growing pains should now be all but over.
;;; Internally, @code{htmlprag-internal:error} introduced for Bigloo
;;; portability. SISC returned to the test list; thanks to Scott G. Miller
;;; for his help. Fixed a new character @code{eq?} bug, thanks to SISC.
;;;
;;; @item Version 0.10 --- 11 May 2004
;;; All public identifiers have been renamed to drop the ``@code{htmlprag:}''
;;; prefix. The portability identifiers have been renamed to begin with an
;;; @code{htmlprag-internal:} prefix, are now considered strictly
;;; internal-use-only, and have otherwise been changed. @code{parse-html} and
;;; @code{always-empty-html-elements} are no longer public.
;;; @code{test-htmlprag} now tests @code{html->sxml} rather than
;;; @code{parse-html}. SISC temporarily removed from the test list, until an
;;; open source Java that works correctly is found.
;;;
;;; @item Version 0.9 --- 7 May 2004
;;; HTML encoding procedures added. Added
;;; @code{htmlprag:sxml-html-entity-value}. Upper-case @code{X} in hexadecimal
;;; character entities is now parsed, in addition to lower-case @code{x}.
;;; Added @code{htmlprag:always-empty-html-elements}. Added additional
;;; portability bindings. Added more test cases.
;;;
;;; @item Version 0.8 --- 27 April 2004
;;; Entity references (symbolic, decimal numeric, hexadecimal numeric) are now
;;; parsed into @code{*ENTITY*} SXML. SXML symbols like @code{*TOP*} are now
;;; always upper-case, regardless of the Scheme implementation. Identifiers
;;; such as @code{htmlprag:sxml-top-symbol} are bound to the upper-case
;;; symbols. Procedures @code{htmlprag:html->sxml-0nf},
;;; @code{htmlprag:html->sxml-1nf}, and @code{htmlprag:html->sxml-2nf} have
;;; been added. @code{htmlprag:html->sxml} now an alias for
;;; @code{htmlprag:html->sxml-0nf}. @code{htmlprag:parse} has been refashioned
;;; as @code{htmlprag:parse-html} and should no longer be directly. A number
;;; of identifiers have been renamed to be more appropriate when the
;;; @code{htmlprag:} prefix is dropped in some implementation-specific
;;; packagings of HtmlPrag: @code{htmlprag:make-tokenizer} to
;;; @code{htmlprag:make-html-tokenizer}, @code{htmlprag:parse/tokenizer} to
;;; @code{htmlprag:parse-html/tokenizer}, @code{htmlprag:html->token-list} to
;;; @code{htmlprag:tokenize-html}, @code{htmlprag:token-kind} to
;;; @code{htmlprag:sxml-token-kind}, and @code{htmlprag:test} to
;;; @code{htmlprag:test-htmlprag}. Verbatim elements with empty-element tag
;;; syntax are handled correctly. New versions of Bigloo and RScheme tested.
;;;
;;; @item Version 0.7 --- 10 March 2004
;;; Verbatim pair elements like @code{script} and @code{xmp} are now parsed
;;; correctly. Two Scheme implementations have temporarily been dropped from
;;; regression testing: Kawa, due to a Java bytecode verifier error likely due
;;; to a Java installation problem on the test machine; and SXM 1.1, due to
;;; hitting a limit on the number of literals late in the test suite code.
;;; Tested newer versions of Bigloo, Chicken, Gauche, Guile, MIT Scheme, PLT
;;; MzScheme, RScheme, SISC, and STklos. RScheme no longer requires the
;;; ``@code{(define get-output-string close-output-port)}'' workaround.
;;;
;;; @item Version 0.6 --- 3 July 2003
;;; Fixed uses of @code{eq?} in character comparisons, thanks to Scott G.
;;; Miller. Added @code{htmlprag:html->normalized-sxml} and
;;; @code{htmlprag:html->nonnormalized-sxml}. Started to add
;;; @code{close-output-port} to uses of output strings, then reverted due to
;;; bug in one of the supported dialects. Tested newer versions of Bigloo,
;;; Gauche, PLT MzScheme, RScheme.
;;;
;;; @item Version 0.5 --- 26 February 2003
;;; Removed uses of @code{call-with-values}. Re-ordered top-level definitions,
;;; for portability. Now tests under Kawa 1.6.99, RScheme 0.7.3.2, Scheme 48
;;; 0.57, SISC 1.7.4, STklos 0.54, and SXM 1.1.
;;;
;;; @item Version 0.4 --- 19 February 2003
;;; Apostrophe-quoted element attribute values are now handled. A bug that
;;; incorrectly assumed left-to-right term evaluation order has been fixed
;;; (thanks to MIT Scheme for confronting us with this). Now also tests OK
;;; under Gauche 0.6.6 and MIT Scheme 7.7.1. Portability improvement for
;;; implementations (e.g., RScheme 0.7.3.2.b6, Stalin 0.9) that cannot read
;;; @code{@@} as a symbol (although those implementations tend to present other
;;; portability issues, as yet unresolved).
;;;
;;; @item Version 0.3 --- 5 February 2003
;;; A test suite with 66 cases has been added, and necessary changes have been
;;; made for the suite to pass on five popular Scheme implementations. XML
;;; processing instructions are now parsed. Parent constraints have been added
;;; for @code{colgroup}, @code{tbody}, and @code{thead} elements. Erroneous
;;; input, including invalid hexadecimal entity reference syntax and extraneous
;;; double quotes in element tags, is now parsed better.
;;; @code{htmlprag:token-kind} emits symbols more consistent with SXML.
;;;
;;; @item Version 0.2 --- 2 February 2003
;;; Portability improvements.
;;;
;;; @item Version 0.1 --- 31 January 2003
;;; Dusted off old Guile-specific code from April 2001, converted to emit SXML,
;;; mostly ported to R5RS and SRFI-6, added some XHTML support and
;;; documentation. A little preliminary testing has been done, and the package
;;; is already useful for some applications, but this release should be
;;; considered a preview to invite comments.
;;;
;;; @end table
;;; @unnumberedsec References
;;; @table @asis
;;;
;;; @item [HTML]
;;; Dave Raggett, Arnaud Le Hors, Ian Jacobs, eds., ``HTML 4.01
;;; Specification,'' W3C Recommendation, 24 December 1999.@*
;;; @uref{http://www.w3.org/TR/1999/REC-html401-19991224/}
;;;
;;; @item [LGPL]
;;; Free Software Foundation, ``GNU Lesser General Public License,'' Version
;;; 2.1, February 1999, 59 Temple Place, Suite 330, Boston, MA 02111-1307
;;; USA.@*
;;; @uref{http://www.gnu.org/copyleft/lesser.html}
;;;
;;; @item [SRFI-6]
;;; William D. Clinger, ``Basic String Ports,'' SRFI 6, 1 July 1999.@*
;;; @uref{http://srfi.schemers.org/srfi-6/srfi-6.html}
;;;
;;; @item [SRFI-23]
;;; Stephan Houben, ``Error reporting mechanism,'' SRFI 23, 26 April 2001.@*
;;; @uref{http://srfi.schemers.org/srfi-23/srfi-23.html}
;;;
;;; @item [SSAX]
;;; Oleg Kiselyov, ``A functional-style framework to parse XML documents,''
;;; 5 September 2002.@*
;;; @uref{http://pobox.com/~oleg/ftp/Scheme/xml.html#XML-parser}
;;;
;;; @item [SSAX-HTML]
;;; Oleg Kiselyov, ``Permissive parsing of perhaps invalid HTML,'' Version 1.1,
;;; 3 November 2001.@*
;;; @uref{http://pobox.com/~oleg/ftp/Scheme/xml.html#HTML-parser}
;;;
;;; @item [SXML]
;;; Oleg Kiselyov, ``SXML,'' revision 3.0.@*
;;; @uref{http://pobox.com/~oleg/ftp/Scheme/SXML.html}
;;;
;;; @item [SXML-Tools]
;;; Kirill Lisovsky, ``SXPath and SXPointer,''@*
;;; @uref{http://pair.com/lisovsky/query/sxpath/}
;;;
;;; @item [SXPath]
;;; Oleg Kiselyov, ``SXPath,'' version 3.5, 12 January 2001.@*
;;; @uref{http://pobox.com/~oleg/ftp/Scheme/xml.html#SXPath}
;;;
;;; @item [XHTML]
;;; ``XHTML 1.0: The Extensible HyperText Markup Language: A Reformulation of
;;; HTML 4 in XML 1.0,'' W3C Recommendation, 26 January 2000.@*
;;; @uref{http://www.w3.org/TR/2000/REC-xhtml1-20000126/}
;;;
;;; @item [XML-Names]
;;; Tim Bray, Dave Hollander, Andrew Layman, eds., ``Namespaces in XML,'' W3C
;;; Recommendation, 14 January 1999.@*
;;; @uref{http://www.w3.org/TR/1999/REC-xml-names-19990114/}
;;;
;;; @end table
;; ############## END CANONICAL htmlprag.scm ##############
(export
shtml-comment-symbol
shtml-decl-symbol
shtml-empty-symbol
shtml-end-symbol
shtml-entity-symbol
shtml-pi-symbol
shtml-start-symbol
shtml-text-symbol
shtml-top-symbol
shtml-named-char-id
shtml-numeric-char-id
shtml-entity-value
make-html-tokenizer
tokenize-html
shtml-token-kind
parse-html/tokenizer
html->sxml-0nf
html->sxml-1nf
html->sxml-2nf
html->sxml
html->shtml
write-shtml-as-html
shtml->html
sxml->html
write-sxml-html
test-htmlprag
)
;;; arch-tag: 491d7e61-5690-4b76-bc8f-d70315c10ed5
;;; htmlprag.scm ends here
guile-lib-0.2.6.1/src/graph/ 0000775 0000764 0000764 00000000000 13320656103 012431 5 0000000 0000000 guile-lib-0.2.6.1/src/graph/topological-sort.scm 0000664 0000764 0000764 00000005531 13314016560 016362 0000000 0000000 ;; (graph topological-sort) -- topological sorting
;; Written 1995 by Mikael Durfeldt.
;; This file is based on tsort.scm from SLIB, and is in the public domain.
;;; Commentary:
;; The algorithm is inspired by Cormen, Leiserson and Rivest (1990)
;; ``Introduction to Algorithms'', chapter 23.
;;; Code:
(define-module (graph topological-sort)
#:export (topological-sort
topological-sortq
topological-sortv)
#:use-module (math primes))
(define (topological-sort-helper dag insert lookup)
(if (null? dag)
'()
(let* ((adj-table (make-hash-table
(car (primes> (length dag) 1))))
(sorted '()))
(letrec ((visit
(lambda (u adj-list)
;; Color vertex u
(insert adj-table u 'colored)
;; Visit uncolored vertices which u connects to
(for-each (lambda (v)
(let ((val (lookup adj-table v)))
(if (not (eq? val 'colored))
(visit v (or val '())))))
adj-list)
;; Since all vertices downstream u are visited
;; by now, we can safely put u on the output list
(set! sorted (cons u sorted)))))
;; Hash adjacency lists
(for-each (lambda (def)
(insert adj-table (car def) (cdr def)))
(cdr dag))
;; Visit vertices
(visit (caar dag) (cdar dag))
(for-each (lambda (def)
(let ((val (lookup adj-table (car def))))
(if (not (eq? val 'colored))
(visit (car def) (cdr def)))))
(cdr dag)))
sorted)))
(define (topological-sort dag)
"Returns a list of the objects in the directed acyclic graph, @var{dag}, topologically sorted. Objects are
compared using @code{equal?}. The graph has the form:
@lisp
(list (obj1 . (dependents-of-obj1))
(obj2 . (dependents-of-obj2)) ...)
@end lisp
...specifying, for example, that @code{obj1} must come before all the objects in @code{(dependents-of-obj1)} in
the sort."
(topological-sort-helper dag hash-set! hash-ref))
(define (topological-sortq dag)
"Returns a list of the objects in the directed acyclic graph, @var{dag}, topologically sorted. Objects are
compared using @code{eq?}. The graph has the form:
@lisp
(list (obj1 . (dependents-of-obj1))
(obj2 . (dependents-of-obj2)) ...)
@end lisp
...specifying, for example, that @code{obj1} must come before all the objects in @code{(dependents-of-obj1)} in
the sort."
(topological-sort-helper dag hashq-set! hashq-ref))
(define (topological-sortv dag)
"Returns a list of the objects in the directed acyclic graph, @var{dag}, topologically sorted. Objects are
compared using @code{eqv?}. The graph has the form:
@lisp
(list (obj1 . (dependents-of-obj1))
(obj2 . (dependents-of-obj2)) ...)
@end lisp
...specifying, for example, that @code{obj1} must come before all the objects in @code{(dependents-of-obj1)} in
the sort."
(topological-sort-helper dag hashv-set! hashv-ref))
;;; arch-tag: 9ef30b53-688a-43fc-b208-df78d5b38c74
guile-lib-0.2.6.1/src/text/ 0000775 0000764 0000764 00000000000 13320656103 012314 5 0000000 0000000 guile-lib-0.2.6.1/src/text/parse-lalr.scm 0000664 0000764 0000764 00000154166 13314016560 015017 0000000 0000000 ;; (text parse-lalr) -- yacc's parser generator, in Guile
;; Copyright (C) 1984,1989,1990,2013 Free Software Foundation, Inc.
;; Copyright (C) 1996-2002 Dominique Boucher
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;; ---------------------------------------------------------------------- ;;
#!
;;; Commentary:
This file contains yet another LALR(1) parser generator written in
Scheme. In contrast to other such parser generators, this one
implements a more efficient algorithm for computing the lookahead sets.
The algorithm is the same as used in Bison (GNU yacc) and is described
in the following paper:
"Efficient Computation of LALR(1) Look-Ahead Set", F. DeRemer and
T. Pennello, TOPLAS, vol. 4, no. 4, october 1982.
As a consequence, it is not written in a fully functional style.
In fact, much of the code is a direct translation from C to Scheme
of the Bison sources.
@section Defining a parser
The module @code{(text parse-lalr)} declares a macro called @code{lalr-parser}:
@lisp
(lalr-parser tokens rules ...)
@end lisp
This macro, when given appropriate arguments, generates an LALR(1)
syntax analyzer. The macro accepts at least two arguments. The first
is a list of symbols which represent the terminal symbols of the
grammar. The remaining arguments are the grammar production rules.
@section Running the parser
The parser generated by the @code{lalr-parser} macro is a function that
takes two parameters. The first parameter is a lexical analyzer while
the second is an error procedure.
The lexical analyzer is zero-argument function (a thunk)
invoked each time the parser needs to look-ahead in the token stream.
A token is usually a pair whose @code{car} is the symbol corresponding to
the token (the same symbol as used in the grammar definition). The
@code{cdr} of the pair is the semantic value associated with the token. For
example, a string token would have the @code{car} set to @code{'string}
while the @code{cdr} is set to the string value @code{"hello"}.
Once the end of file is encountered, the lexical analyzer must always
return the symbol @code{'*eoi*} each time it is invoked.
The error procedure must be a function that accepts at least two
parameters.
@section The grammar format
The grammar is specified by first giving the list of terminals and the
list of non-terminal definitions. Each non-terminal definition
is a list where the first element is the non-terminal and the other
elements are the right-hand sides (lists of grammar symbols). In
addition to this, each rhs can be followed by a semantic action.
For example, consider the following (yacc) grammar for a very simple
expression language:
@example
e : e '+' t
| e '-' t
| t
;
t : t '*' f
: t '/' f
| f
;
f : ID
;
@end example
The same grammar, written for the scheme parser generator, would look
like this (with semantic actions)
@lisp
(define expr-parser
(lalr-parser
; Terminal symbols
(ID + - * /)
; Productions
(e (e + t) : (+ $1 $3)
(e - t) : (- $1 $3)
(t) : $1)
(t (t * f) : (* $1 $3)
(t / f) : (/ $1 $3)
(f) : $1)
(f (ID) : $1)))
@end lisp
In semantic actions, the symbol @code{$n} refers to the synthesized
attribute value of the nth symbol in the production. The value
associated with the non-terminal on the left is the result of
evaluating the semantic action (it defaults to @code{#f}).
The above grammar implicitly handles operator precedences. It is also
possible to explicitly assign precedences and associativity to
terminal symbols and productions a la Yacc. Here is a modified
(and augmented) version of the grammar:
@lisp
(define expr-parser
(lalr-parser
; Terminal symbols
(ID
(left: + -)
(left: * /)
(nonassoc: uminus))
(e (e + e) : (+ $1 $3)
(e - e) : (- $1 $3)
(e * e) : (* $1 $3)
(e / e) : (/ $1 $3)
(- e (prec: uminus)) : (- $2)
(ID) : $1)))
@end lisp
The @code{left:} directive is used to specify a set of left-associative
operators of the same precedence level, the @code{right:} directive for
right-associative operators, and @code{nonassoc:} for operators that
are not associative. Note the use of the (apparently) useless
terminal @code{uminus}. It is only defined in order to assign to the
penultimate rule a precedence level higher than that of @code{*} and
@code{/}. The @code{prec:} directive can only appear as the last element of a
rule. Finally, note that precedence levels are incremented from
left to right, i.e. the precedence level of @code{+} and @code{-} is less
than the precedence level of @code{*} and @code{/} since the formers appear
first in the list of terminal symbols (token definitions).
@section A final note on conflict resolution
Conflicts in the grammar are handled in a conventional way.
In the absence of precedence directives,
Shift/Reduce conflicts are resolved by shifting, and Reduce/Reduce
conflicts are resolved by choosing the rule listed first in the
grammar definition.
You can print the states of the generated parser by evaluating
@code{(print-states)}. The format of the output is similar to the one
produced by bison when given the -v command-line option.
;;; Code:
!#
;;; ---------- SYSTEM DEPENDENT SECTION -----------------
;; put in a module by Richard Todd
(define-module (text parse-lalr)
#:use-module (scheme documentation)
#:export (lalr-parser
print-states))
;; this code is by Thien-Thi Nguyen, found in a google search
(begin
(defmacro def-macro (form . body)
`(defmacro ,(car form) ,(cdr form) ,@body))
(def-macro (BITS-PER-WORD) 28)
(def-macro (lalr-error msg obj) `(throw 'lalr-error ,msg ,obj))
(def-macro (logical-or x . y) `(logior ,x ,@y)))
;;; ---------- END OF SYSTEM DEPENDENT SECTION ------------
;; - Macros pour la gestion des vecteurs de bits
(def-macro (set-bit v b)
`(let ((x (quotient ,b (BITS-PER-WORD)))
(y (expt 2 (remainder ,b (BITS-PER-WORD)))))
(vector-set! ,v x (logical-or (vector-ref ,v x) y))))
(def-macro (bit-union v1 v2 n)
`(do ((i 0 (+ i 1)))
((= i ,n))
(vector-set! ,v1 i (logical-or (vector-ref ,v1 i)
(vector-ref ,v2 i)))))
;; - Macro pour les structures de donnees
(def-macro (new-core) `(make-vector 4 0))
(def-macro (set-core-number! c n) `(vector-set! ,c 0 ,n))
(def-macro (set-core-acc-sym! c s) `(vector-set! ,c 1 ,s))
(def-macro (set-core-nitems! c n) `(vector-set! ,c 2 ,n))
(def-macro (set-core-items! c i) `(vector-set! ,c 3 ,i))
(def-macro (core-number c) `(vector-ref ,c 0))
(def-macro (core-acc-sym c) `(vector-ref ,c 1))
(def-macro (core-nitems c) `(vector-ref ,c 2))
(def-macro (core-items c) `(vector-ref ,c 3))
(def-macro (new-shift) `(make-vector 3 0))
(def-macro (set-shift-number! c x) `(vector-set! ,c 0 ,x))
(def-macro (set-shift-nshifts! c x) `(vector-set! ,c 1 ,x))
(def-macro (set-shift-shifts! c x) `(vector-set! ,c 2 ,x))
(def-macro (shift-number s) `(vector-ref ,s 0))
(def-macro (shift-nshifts s) `(vector-ref ,s 1))
(def-macro (shift-shifts s) `(vector-ref ,s 2))
(def-macro (new-red) `(make-vector 3 0))
(def-macro (set-red-number! c x) `(vector-set! ,c 0 ,x))
(def-macro (set-red-nreds! c x) `(vector-set! ,c 1 ,x))
(def-macro (set-red-rules! c x) `(vector-set! ,c 2 ,x))
(def-macro (red-number c) `(vector-ref ,c 0))
(def-macro (red-nreds c) `(vector-ref ,c 1))
(def-macro (red-rules c) `(vector-ref ,c 2))
(def-macro (new-set nelem)
`(make-vector ,nelem 0))
(def-macro (vector-map f v)
`(let ((vm-n (- (vector-length ,v) 1)))
(let loop ((vm-low 0) (vm-high vm-n))
(if (= vm-low vm-high)
(vector-set! ,v vm-low (,f (vector-ref ,v vm-low) vm-low))
(let ((vm-middle (quotient (+ vm-low vm-high) 2)))
(loop vm-low vm-middle)
(loop (+ vm-middle 1) vm-high))))))
;; - Constantes
(define STATE-TABLE-SIZE 1009)
;; - Tableaux
(define rrhs #f)
(define rlhs #f)
(define ritem #f)
(define nullable #f)
(define derives #f)
(define fderives #f)
(define firsts #f)
(define kernel-base #f)
(define kernel-end #f)
(define shift-symbol #f)
(define shift-set #f)
(define red-set #f)
(define state-table #f)
(define acces-symbol #f)
(define reduction-table #f)
(define shift-table #f)
(define consistent #f)
(define lookaheads #f)
(define LA #f)
(define LAruleno #f)
(define lookback #f)
(define goto-map #f)
(define from-state #f)
(define to-state #f)
(define includes #f)
(define F #f)
(define action-table #f)
;; - Variables
(define nitems #f)
(define nrules #f)
(define nvars #f)
(define nterms #f)
(define nsyms #f)
(define nstates #f)
(define first-state #f)
(define last-state #f)
(define final-state #f)
(define first-shift #f)
(define last-shift #f)
(define first-reduction #f)
(define last-reduction #f)
(define nshifts #f)
(define maxrhs #f)
(define ngotos #f)
(define token-set-size #f)
(define (gen-tables! tokens gram)
(initialize-all)
(rewrite-grammar
tokens
gram
(lambda (terms terms/prec vars gram gram/actions)
(set! the-terminals/prec (list->vector terms/prec))
(set! the-terminals (list->vector terms))
(set! the-nonterminals (list->vector vars))
(set! nterms (length terms))
(set! nvars (length vars))
(set! nsyms (+ nterms nvars))
(let ((no-of-rules (length gram/actions))
(no-of-items (let loop ((l gram/actions) (count 0))
(if (null? l)
count
(loop (cdr l) (+ count (length (caar l))))))))
(pack-grammar no-of-rules no-of-items gram)
(set-derives)
(set-nullable)
(generate-states)
(lalr)
(build-tables)
(compact-action-table terms)
gram/actions))))
(define (initialize-all)
(set! rrhs #f)
(set! rlhs #f)
(set! ritem #f)
(set! nullable #f)
(set! derives #f)
(set! fderives #f)
(set! firsts #f)
(set! kernel-base #f)
(set! kernel-end #f)
(set! shift-symbol #f)
(set! shift-set #f)
(set! red-set #f)
(set! state-table (make-vector STATE-TABLE-SIZE '()))
(set! acces-symbol #f)
(set! reduction-table #f)
(set! shift-table #f)
(set! consistent #f)
(set! lookaheads #f)
(set! LA #f)
(set! LAruleno #f)
(set! lookback #f)
(set! goto-map #f)
(set! from-state #f)
(set! to-state #f)
(set! includes #f)
(set! F #f)
(set! action-table #f)
(set! nstates #f)
(set! first-state #f)
(set! last-state #f)
(set! final-state #f)
(set! first-shift #f)
(set! last-shift #f)
(set! first-reduction #f)
(set! last-reduction #f)
(set! nshifts #f)
(set! maxrhs #f)
(set! ngotos #f)
(set! token-set-size #f)
(set! rule-precedences '()))
(define (pack-grammar no-of-rules no-of-items gram)
(set! nrules (+ no-of-rules 1))
(set! nitems no-of-items)
(set! rlhs (make-vector nrules #f))
(set! rrhs (make-vector nrules #f))
(set! ritem (make-vector (+ 1 nitems) #f))
(let loop ((p gram) (item-no 0) (rule-no 1))
(if (not (null? p))
(let ((nt (caar p)))
(let loop2 ((prods (cdar p)) (it-no2 item-no) (rl-no2 rule-no))
(if (null? prods)
(loop (cdr p) it-no2 rl-no2)
(begin
(vector-set! rlhs rl-no2 nt)
(vector-set! rrhs rl-no2 it-no2)
(let loop3 ((rhs (car prods)) (it-no3 it-no2))
(if (null? rhs)
(begin
(vector-set! ritem it-no3 (- rl-no2))
(loop2 (cdr prods) (+ it-no3 1) (+ rl-no2 1)))
(begin
(vector-set! ritem it-no3 (car rhs))
(loop3 (cdr rhs) (+ it-no3 1))))))))))))
;; Fonction set-derives
;; --------------------
(define (set-derives)
(define delts (make-vector (+ nrules 1) 0))
(define dset (make-vector nvars -1))
(let loop ((i 1) (j 0)) ; i = 0
(if (< i nrules)
(let ((lhs (vector-ref rlhs i)))
(if (>= lhs 0)
(begin
(vector-set! delts j (cons i (vector-ref dset lhs)))
(vector-set! dset lhs j)
(loop (+ i 1) (+ j 1)))
(loop (+ i 1) j)))))
(set! derives (make-vector nvars 0))
(let loop ((i 0))
(if (< i nvars)
(let ((q (let loop2 ((j (vector-ref dset i)) (s '()))
(if (< j 0)
s
(let ((x (vector-ref delts j)))
(loop2 (cdr x) (cons (car x) s)))))))
(vector-set! derives i q)
(loop (+ i 1))))))
(define (set-nullable)
(set! nullable (make-vector nvars #f))
(let ((squeue (make-vector nvars #f))
(rcount (make-vector (+ nrules 1) 0))
(rsets (make-vector nvars #f))
(relts (make-vector (+ nitems nvars 1) #f)))
(let loop ((r 0) (s2 0) (p 0))
(let ((*r (vector-ref ritem r)))
(if *r
(if (< *r 0)
(let ((symbol (vector-ref rlhs (- *r))))
(if (and (>= symbol 0)
(not (vector-ref nullable symbol)))
(begin
(vector-set! nullable symbol #t)
(vector-set! squeue s2 symbol)
(loop (+ r 1) (+ s2 1) p))))
(let loop2 ((r1 r) (any-tokens #f))
(let* ((symbol (vector-ref ritem r1)))
(if (> symbol 0)
(loop2 (+ r1 1) (or any-tokens (>= symbol nvars)))
(if (not any-tokens)
(let ((ruleno (- symbol)))
(let loop3 ((r2 r) (p2 p))
(let ((symbol (vector-ref ritem r2)))
(if (> symbol 0)
(begin
(vector-set! rcount ruleno
(+ (vector-ref rcount ruleno) 1))
(vector-set! relts p2
(cons (vector-ref rsets symbol)
ruleno))
(vector-set! rsets symbol p2)
(loop3 (+ r2 1) (+ p2 1)))
(loop (+ r2 1) s2 p2)))))
(loop (+ r1 1) s2 p))))))
(let loop ((s1 0) (s3 s2))
(if (< s1 s3)
(let loop2 ((p (vector-ref rsets (vector-ref squeue s1))) (s4 s3))
(if p
(let* ((x (vector-ref relts p))
(ruleno (cdr x))
(y (- (vector-ref rcount ruleno) 1)))
(vector-set! rcount ruleno y)
(if (= y 0)
(let ((symbol (vector-ref rlhs ruleno)))
(if (and (>= symbol 0)
(not (vector-ref nullable symbol)))
(begin
(vector-set! nullable symbol #t)
(vector-set! squeue s4 symbol)
(loop2 (car x) (+ s4 1)))
(loop2 (car x) s4)))
(loop2 (car x) s4))))
(loop (+ s1 1) s4)))))))))
; Fonction set-firsts qui calcule un tableau de taille
; nvars et qui donne, pour chaque non-terminal X, une liste des
; non-terminaux pouvant apparaitre au debut d'une derivation a
; partir de X.
(define (set-firsts)
(set! firsts (make-vector nvars '()))
;; -- initialization
(let loop ((i 0))
(if (< i nvars)
(let loop2 ((sp (vector-ref derives i)))
(if (null? sp)
(loop (+ i 1))
(let ((sym (vector-ref ritem (vector-ref rrhs (car sp)))))
(if (< -1 sym nvars)
(vector-set! firsts i (sinsert sym (vector-ref firsts i))))
(loop2 (cdr sp)))))))
;; -- reflexive and transitive closure
(let loop ((continue #t))
(if continue
(let loop2 ((i 0) (cont #f))
(if (>= i nvars)
(loop cont)
(let* ((x (vector-ref firsts i))
(y (let loop3 ((l x) (z x))
(if (null? l)
z
(loop3 (cdr l)
(sunion (vector-ref firsts (car l)) z))))))
(if (equal? x y)
(loop2 (+ i 1) cont)
(begin
(vector-set! firsts i y)
(loop2 (+ i 1) #t))))))))
(let loop ((i 0))
(if (< i nvars)
(begin
(vector-set! firsts i (sinsert i (vector-ref firsts i)))
(loop (+ i 1))))))
; Fonction set-fderives qui calcule un tableau de taille
; nvars et qui donne, pour chaque non-terminal, une liste des regles pouvant
; etre derivees a partir de ce non-terminal. (se sert de firsts)
(define (set-fderives)
(set! fderives (make-vector nvars #f))
(set-firsts)
(let loop ((i 0))
(if (< i nvars)
(let ((x (let loop2 ((l (vector-ref firsts i)) (fd '()))
(if (null? l)
fd
(loop2 (cdr l)
(sunion (vector-ref derives (car l)) fd))))))
(vector-set! fderives i x)
(loop (+ i 1))))))
; Fonction calculant la fermeture d'un ensemble d'items LR0
; ou core est une liste d'items
(define (closure core)
;; Initialization
(define ruleset (make-vector nrules #f))
(let loop ((csp core))
(if (not (null? csp))
(let ((sym (vector-ref ritem (car csp))))
(if (< -1 sym nvars)
(let loop2 ((dsp (vector-ref fderives sym)))
(if (not (null? dsp))
(begin
(vector-set! ruleset (car dsp) #t)
(loop2 (cdr dsp))))))
(loop (cdr csp)))))
(let loop ((ruleno 1) (csp core) (itemsetv '())) ; ruleno = 0
(if (< ruleno nrules)
(if (vector-ref ruleset ruleno)
(let ((itemno (vector-ref rrhs ruleno)))
(let loop2 ((c csp) (itemsetv2 itemsetv))
(if (and (pair? c)
(< (car c) itemno))
(loop2 (cdr c) (cons (car c) itemsetv2))
(loop (+ ruleno 1) c (cons itemno itemsetv2)))))
(loop (+ ruleno 1) csp itemsetv))
(let loop2 ((c csp) (itemsetv2 itemsetv))
(if (pair? c)
(loop2 (cdr c) (cons (car c) itemsetv2))
(reverse itemsetv2))))))
(define (allocate-item-sets)
(set! kernel-base (make-vector nsyms 0))
(set! kernel-end (make-vector nsyms #f)))
(define (allocate-storage)
(allocate-item-sets)
(set! red-set (make-vector (+ nrules 1) 0)))
;; --
(define (initialize-states)
(let ((p (new-core)))
(set-core-number! p 0)
(set-core-acc-sym! p #f)
(set-core-nitems! p 1)
(set-core-items! p '(0))
(set! first-state (list p))
(set! last-state first-state)
(set! nstates 1)))
(define (generate-states)
(allocate-storage)
(set-fderives)
(initialize-states)
(let loop ((this-state first-state))
(if (pair? this-state)
(let* ((x (car this-state))
(is (closure (core-items x))))
(save-reductions x is)
(new-itemsets is)
(append-states)
(if (> nshifts 0)
(save-shifts x))
(loop (cdr this-state))))))
;; Fonction calculant les symboles sur lesquels il faut "shifter"
;; et regroupe les items en fonction de ces symboles
(define (new-itemsets itemset)
;; - Initialization
(set! shift-symbol '())
(let loop ((i 0))
(if (< i nsyms)
(begin
(vector-set! kernel-end i '())
(loop (+ i 1)))))
(let loop ((isp itemset))
(if (pair? isp)
(let* ((i (car isp))
(sym (vector-ref ritem i)))
(if (>= sym 0)
(begin
(set! shift-symbol (sinsert sym shift-symbol))
(let ((x (vector-ref kernel-end sym)))
(if (null? x)
(begin
(vector-set! kernel-base sym (cons (+ i 1) x))
(vector-set! kernel-end sym (vector-ref kernel-base sym)))
(begin
(set-cdr! x (list (+ i 1)))
(vector-set! kernel-end sym (cdr x)))))))
(loop (cdr isp)))))
(set! nshifts (length shift-symbol)))
(define (get-state sym)
(let* ((isp (vector-ref kernel-base sym))
(n (length isp))
(key (let loop ((isp1 isp) (k 0))
(if (null? isp1)
(modulo k STATE-TABLE-SIZE)
(loop (cdr isp1) (+ k (car isp1))))))
(sp (vector-ref state-table key)))
(if (null? sp)
(let ((x (new-state sym)))
(vector-set! state-table key (list x))
(core-number x))
(let loop ((sp1 sp))
(if (and (= n (core-nitems (car sp1)))
(let loop2 ((i1 isp) (t (core-items (car sp1))))
(if (and (pair? i1)
(= (car i1)
(car t)))
(loop2 (cdr i1) (cdr t))
(null? i1))))
(core-number (car sp1))
(if (null? (cdr sp1))
(let ((x (new-state sym)))
(set-cdr! sp1 (list x))
(core-number x))
(loop (cdr sp1))))))))
(define (new-state sym)
(let* ((isp (vector-ref kernel-base sym))
(n (length isp))
(p (new-core)))
(set-core-number! p nstates)
(set-core-acc-sym! p sym)
(if (= sym nvars) (set! final-state nstates))
(set-core-nitems! p n)
(set-core-items! p isp)
(set-cdr! last-state (list p))
(set! last-state (cdr last-state))
(set! nstates (+ nstates 1))
p))
;; --
(define (append-states)
(set! shift-set
(let loop ((l (reverse shift-symbol)))
(if (null? l)
'()
(cons (get-state (car l)) (loop (cdr l)))))))
;; --
(define (save-shifts core)
(let ((p (new-shift)))
(set-shift-number! p (core-number core))
(set-shift-nshifts! p nshifts)
(set-shift-shifts! p shift-set)
(if last-shift
(begin
(set-cdr! last-shift (list p))
(set! last-shift (cdr last-shift)))
(begin
(set! first-shift (list p))
(set! last-shift first-shift)))))
(define (save-reductions core itemset)
(let ((rs (let loop ((l itemset))
(if (null? l)
'()
(let ((item (vector-ref ritem (car l))))
(if (< item 0)
(cons (- item) (loop (cdr l)))
(loop (cdr l))))))))
(if (pair? rs)
(let ((p (new-red)))
(set-red-number! p (core-number core))
(set-red-nreds! p (length rs))
(set-red-rules! p rs)
(if last-reduction
(begin
(set-cdr! last-reduction (list p))
(set! last-reduction (cdr last-reduction)))
(begin
(set! first-reduction (list p))
(set! last-reduction first-reduction)))))))
;; --
(define (lalr)
(set! token-set-size (+ 1 (quotient nterms (BITS-PER-WORD))))
(set-accessing-symbol)
(set-shift-table)
(set-reduction-table)
(set-max-rhs)
(initialize-LA)
(set-goto-map)
(initialize-F)
(build-relations)
(digraph includes)
(compute-lookaheads))
(define (set-accessing-symbol)
(set! acces-symbol (make-vector nstates #f))
(let loop ((l first-state))
(if (pair? l)
(let ((x (car l)))
(vector-set! acces-symbol (core-number x) (core-acc-sym x))
(loop (cdr l))))))
(define (set-shift-table)
(set! shift-table (make-vector nstates #f))
(let loop ((l first-shift))
(if (pair? l)
(let ((x (car l)))
(vector-set! shift-table (shift-number x) x)
(loop (cdr l))))))
(define (set-reduction-table)
(set! reduction-table (make-vector nstates #f))
(let loop ((l first-reduction))
(if (pair? l)
(let ((x (car l)))
(vector-set! reduction-table (red-number x) x)
(loop (cdr l))))))
(define (set-max-rhs)
(let loop ((p 0) (curmax 0) (length 0))
(let ((x (vector-ref ritem p)))
(if x
(if (>= x 0)
(loop (+ p 1) curmax (+ length 1))
(loop (+ p 1) (max curmax length) 0))
(set! maxrhs curmax)))))
(define (initialize-LA)
(define (last l)
(if (null? (cdr l))
(car l)
(last (cdr l))))
(set! consistent (make-vector nstates #f))
(set! lookaheads (make-vector (+ nstates 1) #f))
(let loop ((count 0) (i 0))
(if (< i nstates)
(begin
(vector-set! lookaheads i count)
(let ((rp (vector-ref reduction-table i))
(sp (vector-ref shift-table i)))
(if (and rp
(or (> (red-nreds rp) 1)
(and sp
(not
(< (vector-ref acces-symbol
(last (shift-shifts sp)))
nvars)))))
(loop (+ count (red-nreds rp)) (+ i 1))
(begin
(vector-set! consistent i #t)
(loop count (+ i 1))))))
(begin
(vector-set! lookaheads nstates count)
(let ((c (max count 1)))
(set! LA (make-vector c #f))
(do ((j 0 (+ j 1))) ((= j c)) (vector-set! LA j (new-set token-set-size)))
(set! LAruleno (make-vector c -1))
(set! lookback (make-vector c #f)))
(let loop ((i 0) (np 0))
(if (< i nstates)
(if (vector-ref consistent i)
(loop (+ i 1) np)
(let ((rp (vector-ref reduction-table i)))
(if rp
(let loop2 ((j (red-rules rp)) (np2 np))
(if (null? j)
(loop (+ i 1) np2)
(begin
(vector-set! LAruleno np2 (car j))
(loop2 (cdr j) (+ np2 1)))))
(loop (+ i 1) np))))))))))
(define (set-goto-map)
(set! goto-map (make-vector (+ nvars 1) 0))
(let ((temp-map (make-vector (+ nvars 1) 0)))
(let loop ((ng 0) (sp first-shift))
(if (pair? sp)
(let loop2 ((i (reverse (shift-shifts (car sp)))) (ng2 ng))
(if (pair? i)
(let ((symbol (vector-ref acces-symbol (car i))))
(if (< symbol nvars)
(begin
(vector-set! goto-map symbol
(+ 1 (vector-ref goto-map symbol)))
(loop2 (cdr i) (+ ng2 1)))
(loop2 (cdr i) ng2)))
(loop ng2 (cdr sp))))
(let loop ((k 0) (i 0))
(if (< i nvars)
(begin
(vector-set! temp-map i k)
(loop (+ k (vector-ref goto-map i)) (+ i 1)))
(begin
(do ((i 0 (+ i 1)))
((>= i nvars))
(vector-set! goto-map i (vector-ref temp-map i)))
(set! ngotos ng)
(vector-set! goto-map nvars ngotos)
(vector-set! temp-map nvars ngotos)
(set! from-state (make-vector ngotos #f))
(set! to-state (make-vector ngotos #f))
(do ((sp first-shift (cdr sp)))
((null? sp))
(let* ((x (car sp))
(state1 (shift-number x)))
(do ((i (shift-shifts x) (cdr i)))
((null? i))
(let* ((state2 (car i))
(symbol (vector-ref acces-symbol state2)))
(if (< symbol nvars)
(let ((k (vector-ref temp-map symbol)))
(vector-set! temp-map symbol (+ k 1))
(vector-set! from-state k state1)
(vector-set! to-state k state2))))))))))))))
(define (map-goto state symbol)
(let loop ((low (vector-ref goto-map symbol))
(high (- (vector-ref goto-map (+ symbol 1)) 1)))
(if (> low high)
(begin
(display (list "Error in map-goto" state symbol) (current-error-port))
(newline (current-error-port))
0)
(let* ((middle (quotient (+ low high) 2))
(s (vector-ref from-state middle)))
(cond
((= s state)
middle)
((< s state)
(loop (+ middle 1) high))
(else
(loop low (- middle 1))))))))
(define (initialize-F)
(set! F (make-vector ngotos #f))
(do ((i 0 (+ i 1))) ((= i ngotos)) (vector-set! F i (new-set token-set-size)))
(let ((reads (make-vector ngotos #f)))
(let loop ((i 0) (rowp 0))
(if (< i ngotos)
(let* ((rowf (vector-ref F rowp))
(stateno (vector-ref to-state i))
(sp (vector-ref shift-table stateno)))
(if sp
(let loop2 ((j (shift-shifts sp)) (edges '()))
(if (pair? j)
(let ((symbol (vector-ref acces-symbol (car j))))
(if (< symbol nvars)
(if (vector-ref nullable symbol)
(loop2 (cdr j) (cons (map-goto stateno symbol)
edges))
(loop2 (cdr j) edges))
(begin
(set-bit rowf (- symbol nvars))
(loop2 (cdr j) edges))))
(if (pair? edges)
(vector-set! reads i (reverse edges))))))
(loop (+ i 1) (+ rowp 1)))))
(digraph reads)))
(define (add-lookback-edge stateno ruleno gotono)
(let ((k (vector-ref lookaheads (+ stateno 1))))
(let loop ((found #f) (i (vector-ref lookaheads stateno)))
(if (and (not found) (< i k))
(if (= (vector-ref LAruleno i) ruleno)
(loop #t i)
(loop found (+ i 1)))
(if (not found)
(begin (display "Error in add-lookback-edge : " (current-error-port))
(display (list stateno ruleno gotono) (current-error-port))
(newline (current-error-port)))
(vector-set! lookback i
(cons gotono (vector-ref lookback i))))))))
(define (transpose r-arg n)
(let ((new-end (make-vector n #f))
(new-R (make-vector n #f)))
(do ((i 0 (+ i 1)))
((= i n))
(let ((x (list 'bidon)))
(vector-set! new-R i x)
(vector-set! new-end i x)))
(do ((i 0 (+ i 1)))
((= i n))
(let ((sp (vector-ref r-arg i)))
(if (pair? sp)
(let loop ((sp2 sp))
(if (pair? sp2)
(let* ((x (car sp2))
(y (vector-ref new-end x)))
(set-cdr! y (cons i (cdr y)))
(vector-set! new-end x (cdr y))
(loop (cdr sp2))))))))
(do ((i 0 (+ i 1)))
((= i n))
(vector-set! new-R i (cdr (vector-ref new-R i))))
new-R))
(define (build-relations)
(define (get-state stateno symbol)
(let loop ((j (shift-shifts (vector-ref shift-table stateno)))
(stno stateno))
(if (null? j)
stno
(let ((st2 (car j)))
(if (= (vector-ref acces-symbol st2) symbol)
st2
(loop (cdr j) st2))))))
(set! includes (make-vector ngotos #f))
(do ((i 0 (+ i 1)))
((= i ngotos))
(let ((state1 (vector-ref from-state i))
(symbol1 (vector-ref acces-symbol (vector-ref to-state i))))
(let loop ((rulep (vector-ref derives symbol1))
(edges '()))
(if (pair? rulep)
(let ((*rulep (car rulep)))
(let loop2 ((rp (vector-ref rrhs *rulep))
(stateno state1)
(states (list state1)))
(let ((*rp (vector-ref ritem rp)))
(if (> *rp 0)
(let ((st (get-state stateno *rp)))
(loop2 (+ rp 1) st (cons st states)))
(begin
(if (not (vector-ref consistent stateno))
(add-lookback-edge stateno *rulep i))
(let loop2 ((done #f)
(stp (cdr states))
(rp2 (- rp 1))
(edgp edges))
(if (not done)
(let ((*rp (vector-ref ritem rp2)))
(if (< -1 *rp nvars)
(loop2 (not (vector-ref nullable *rp))
(cdr stp)
(- rp2 1)
(cons (map-goto (car stp) *rp) edgp))
(loop2 #t stp rp2 edgp)))
(loop (cdr rulep) edgp))))))))
(vector-set! includes i edges)))))
(set! includes (transpose includes ngotos)))
(define (compute-lookaheads)
(let ((n (vector-ref lookaheads nstates)))
(let loop ((i 0))
(if (< i n)
(let loop2 ((sp (vector-ref lookback i)))
(if (pair? sp)
(let ((LA-i (vector-ref LA i))
(F-j (vector-ref F (car sp))))
(bit-union LA-i F-j token-set-size)
(loop2 (cdr sp)))
(loop (+ i 1))))))))
(define (digraph relation)
(define infinity (+ ngotos 2))
(define INDEX (make-vector (+ ngotos 1) 0))
(define VERTICES (make-vector (+ ngotos 1) 0))
(define top 0)
(define R relation)
(define (traverse i)
(set! top (+ 1 top))
(vector-set! VERTICES top i)
(let ((height top))
(vector-set! INDEX i height)
(let ((rp (vector-ref R i)))
(if (pair? rp)
(let loop ((rp2 rp))
(if (pair? rp2)
(let ((j (car rp2)))
(if (= 0 (vector-ref INDEX j))
(traverse j))
(if (> (vector-ref INDEX i)
(vector-ref INDEX j))
(vector-set! INDEX i (vector-ref INDEX j)))
(let ((F-i (vector-ref F i))
(F-j (vector-ref F j)))
(bit-union F-i F-j token-set-size))
(loop (cdr rp2))))))
(if (= (vector-ref INDEX i) height)
(let loop ()
(let ((j (vector-ref VERTICES top)))
(set! top (- top 1))
(vector-set! INDEX j infinity)
(if (not (= i j))
(begin
(bit-union (vector-ref F i)
(vector-ref F j)
token-set-size)
(loop)))))))))
(let loop ((i 0))
(if (< i ngotos)
(begin
(if (and (= 0 (vector-ref INDEX i))
(pair? (vector-ref R i)))
(traverse i))
(loop (+ i 1))))))
;; ---------------------------------------------------------------------- ;;
;; operator precedence management ;;
;; ---------------------------------------------------------------------- ;;
; a vector of precedence descriptors where each element
; is of the form (terminal type precedence)
(define the-terminals/prec #f) ; terminal symbols with precedence
; the precedence is an integer >= 0
(define (get-symbol-precedence sym)
(caddr (vector-ref the-terminals/prec sym)))
; the operator type is either 'none, 'left, 'right, or 'nonassoc
(define (get-symbol-assoc sym)
(cadr (vector-ref the-terminals/prec sym)))
(define rule-precedences '())
(define (add-rule-precedence! rule sym)
(set! rule-precedences
(cons (cons rule sym) rule-precedences)))
(define (get-rule-precedence ruleno)
(cond
((assq ruleno rule-precedences)
=> (lambda (p)
(get-symbol-precedence (cdr p))))
(else
;; process the rule symbols from left to right
(let loop ((i (vector-ref rrhs ruleno))
(prec 0))
(let ((item (vector-ref ritem i)))
;; end of rule
(if (< item 0)
prec
(let ((i1 (+ i 1)))
(if (>= item nvars)
;; it's a terminal symbol
(loop i1 (get-symbol-precedence (- item nvars)))
(loop i1 prec)))))))))
;; ---------------------------------------------------------------------- ;;
;; Build the various tables ;;
;; ---------------------------------------------------------------------- ;;
(define (build-tables)
(define (resolve-conflict sym rule)
(let ((sym-prec (get-symbol-precedence sym))
(sym-assoc (get-symbol-assoc sym))
(rule-prec (get-rule-precedence rule)))
(cond
((> sym-prec rule-prec) 'shift)
((< sym-prec rule-prec) 'reduce)
((eq? sym-assoc 'left) 'reduce)
((eq? sym-assoc 'right) 'shift)
(else 'shift))))
;; --- Add an action to the action table ------------------------------ ;;
(define (add-action St Sym Act)
(let* ((x (vector-ref action-table St))
(y (assv Sym x)))
(if y
(if (not (= Act (cdr y)))
;; -- there is a conflict
(begin
(if (and (<= (cdr y) 0)
(<= Act 0))
;; --- reduce/reduce conflict ----------------------- ;;
(begin
(display "%% Reduce/Reduce conflict " (current-error-port))
(display "(reduce " (current-error-port))
(display (- Act) (current-error-port))
(display ", reduce " (current-error-port))
(display (- (cdr y)) (current-error-port))
(display ") on " (current-error-port))
(print-symbol (+ Sym nvars) (current-error-port))
(display " in state " (current-error-port))
(display St (current-error-port))
(newline (current-error-port))
(set-cdr! y (max (cdr y) Act)))
;; --- shift/reduce conflict ------------------------ ;;
;; can we resolve the conflict using precedences?
(case (resolve-conflict Sym (- (cdr y)))
;; -- shift
((shift)
(set-cdr! y Act))
;; -- reduce
((reduce)
#f) ; well, nothing to do...
;; -- signal a conflict!
(else
(display "%% Shift/Reduce conflict " (current-error-port))
(display "(shift " (current-error-port))
(display Act (current-error-port))
(display ", reduce " (current-error-port))
(display (- (cdr y)) (current-error-port))
(display ") on " (current-error-port))
(print-symbol (+ Sym nvars) (current-error-port))
(display " in state " (current-error-port))
(display St (current-error-port))
(newline (current-error-port))
(set-cdr! y Act))))))
(vector-set! action-table St (cons (cons Sym Act) x)))))
(set! action-table (make-vector nstates '()))
(do ((i 0 (+ i 1))) ; i = state
((= i nstates))
(let ((red (vector-ref reduction-table i)))
(if (and red (>= (red-nreds red) 1))
(if (and (= (red-nreds red) 1) (vector-ref consistent i))
(add-action i 'default (- (car (red-rules red))))
(let ((k (vector-ref lookaheads (+ i 1))))
(let loop ((j (vector-ref lookaheads i)))
(if (< j k)
(let ((rule (- (vector-ref LAruleno j)))
(lav (vector-ref LA j)))
(let loop2 ((token 0) (x (vector-ref lav 0)) (y 1) (z 0))
(if (< token nterms)
(begin
(let ((in-la-set? (modulo x 2)))
(if (= in-la-set? 1)
(add-action i token rule)))
(if (= y (BITS-PER-WORD))
(loop2 (+ token 1)
(vector-ref lav (+ z 1))
1
(+ z 1))
(loop2 (+ token 1) (quotient x 2) (+ y 1) z)))))
(loop (+ j 1)))))))))
(let ((shiftp (vector-ref shift-table i)))
(if shiftp
(let loop ((k (shift-shifts shiftp)))
(if (pair? k)
(let* ((state (car k))
(symbol (vector-ref acces-symbol state)))
(if (>= symbol nvars)
(add-action i (- symbol nvars) state))
(loop (cdr k))))))))
(add-action final-state 0 'accept))
(define (compact-action-table terms)
(define (most-common-action acts)
(let ((accums '()))
(let loop ((l acts))
(if (pair? l)
(let* ((x (cdar l))
(y (assv x accums)))
(if (and (number? x) (< x 0))
(if y
(set-cdr! y (+ 1 (cdr y)))
(set! accums (cons `(,x . 1) accums))))
(loop (cdr l)))))
(let loop ((l accums) (max 0) (sym #f))
(if (null? l)
sym
(let ((x (car l)))
(if (> (cdr x) max)
(loop (cdr l) (cdr x) (car x))
(loop (cdr l) max sym)))))))
(define (translate-terms acts)
(map (lambda (act)
(cons (list-ref terms (car act))
(cdr act)))
acts))
(do ((i 0 (+ i 1)))
((= i nstates))
(let ((acts (vector-ref action-table i)))
(if (vector? (vector-ref reduction-table i))
(let ((act (most-common-action acts)))
(vector-set! action-table i
(cons `(*default* . ,(if act act 'error))
(translate-terms
(lalr-filter (lambda (x)
(not (eq? (cdr x) act)))
acts)))))
(vector-set! action-table i
(cons `(*default* . *error*)
(translate-terms acts)))))))
;; --
(define (rewrite-grammar tokens grammar k)
(define eoi '*eoi*)
(define (check-terminal term terms)
(cond
((not (valid-terminal? term))
(lalr-error "invalid terminal: " term))
((member term terms)
(lalr-error "duplicate definition of terminal: " term))))
(define (prec->type prec)
(cdr (assq prec '((left: . left)
(right: . right)
(nonassoc: . nonassoc)))))
(cond
;; --- a few error conditions ---------------------------------------- ;;
((not (list? tokens))
(lalr-error "Invalid token list: " tokens))
((not (pair? grammar))
(lalr-error "Grammar definition must have a non-empty list of productions" '()))
(else
;; --- check the terminals ---------------------------------------- ;;
(let loop1 ((lst tokens)
(rev-terms '())
(rev-terms/prec '())
(prec-level 0))
(if (pair? lst)
(let ((term (car lst)))
(cond
((pair? term)
(if (and (memq (car term) '(left: right: nonassoc:))
(not (null? (cdr term))))
(let ((prec (+ prec-level 1))
(optype (prec->type (car term))))
(let loop-toks ((l (cdr term))
(rev-terms rev-terms)
(rev-terms/prec rev-terms/prec))
(if (null? l)
(loop1 (cdr lst) rev-terms rev-terms/prec prec)
(let ((term (car l)))
(check-terminal term rev-terms)
(loop-toks
(cdr l)
(cons term rev-terms)
(cons (list term optype prec) rev-terms/prec))))))
(lalr-error "invalid operator precedence specification: " term)))
(else
(check-terminal term rev-terms)
(loop1 (cdr lst)
(cons term rev-terms)
(cons (list term 'none 0) rev-terms/prec)
prec-level))))
;; --- check the grammar rules ------------------------------ ;;
(let loop2 ((lst grammar) (rev-nonterm-defs '()))
(if (pair? lst)
(let ((def (car lst)))
(if (not (pair? def))
(lalr-error "Nonterminal definition must be a non-empty list" '())
(let ((nonterm (car def)))
(cond ((not (valid-nonterminal? nonterm))
(lalr-error "Invalid nonterminal:" nonterm))
((or (member nonterm rev-terms)
(assoc nonterm rev-nonterm-defs))
(lalr-error "Nonterminal previously defined:" nonterm))
(else
(loop2 (cdr lst)
(cons def rev-nonterm-defs)))))))
(let* ((terms (cons eoi (reverse rev-terms)))
(terms/prec (cons '(eoi none 0) (reverse rev-terms/prec)))
(nonterm-defs (reverse rev-nonterm-defs))
(nonterms (cons '*start* (map car nonterm-defs))))
(if (= (length nonterms) 1)
(lalr-error "Grammar must contain at least one nonterminal" '())
(let loop-defs ((defs (cons `(*start* (,(cadr nonterms) ,eoi) : $1)
nonterm-defs))
(ruleno 0)
(comp-defs '()))
(if (pair? defs)
(let* ((nonterm-def (car defs))
(compiled-def (rewrite-nonterm-def
nonterm-def
ruleno
terms nonterms)))
(loop-defs (cdr defs)
(+ ruleno (length compiled-def))
(cons compiled-def comp-defs)))
(let ((compiled-nonterm-defs (reverse comp-defs)))
(k terms
terms/prec
nonterms
(map (lambda (x) (cons (caaar x) (map cdar x)))
compiled-nonterm-defs)
(apply append compiled-nonterm-defs))))))))))))))
(define (rewrite-nonterm-def nonterm-def ruleno terms nonterms)
(define No-NT (length nonterms))
(define (encode x)
(let ((PosInNT (pos-in-list x nonterms)))
(if PosInNT
PosInNT
(let ((PosInT (pos-in-list x terms)))
(if PosInT
(+ No-NT PosInT)
(lalr-error "undefined symbol : " x))))))
(define (process-prec-directive rhs ruleno)
(let loop ((l rhs))
(if (null? l)
'()
(let ((first (car l))
(rest (cdr l)))
(cond
((or (member first terms) (member first nonterms))
(cons first (loop rest)))
((and (pair? first)
(eq? (car first) 'prec:))
(pair? (cdr first))
(if (and (pair? (cdr first))
(member (cadr first) terms))
(if (null? (cddr first))
(begin
(add-rule-precedence! ruleno (pos-in-list (cadr first) terms))
(loop rest))
(lalr-error "prec: directive should be at end of rule: " rhs))
(lalr-error "Invalid prec: directive: " first)))
(else
(lalr-error "Invalid terminal or nonterminal: " first)))))))
(if (not (pair? (cdr nonterm-def)))
(lalr-error "At least one production needed for nonterminal" (car nonterm-def))
(let ((name (symbol->string (car nonterm-def))))
(let loop1 ((lst (cdr nonterm-def))
(i 1)
(rev-productions-and-actions '()))
(if (not (pair? lst))
(reverse rev-productions-and-actions)
(let* ((rhs (process-prec-directive (car lst) (+ ruleno i -1)))
(rest (cdr lst))
(prod (map encode (cons (car nonterm-def) rhs))))
(for-each (lambda (x)
(if (not (or (member x terms) (member x nonterms)))
(lalr-error "Invalid terminal or nonterminal" x)))
rhs)
(if (and (pair? rest)
(eq? (car rest) (string->symbol ":"))
(pair? (cdr rest)))
(loop1 (cddr rest)
(+ i 1)
(cons (cons prod (cadr rest))
rev-productions-and-actions))
(let* ((rhs-length (length rhs))
(action
(cons 'vector
(cons (list 'quote (string->symbol
(string-append
name
"-"
(number->string i))))
(let loop-j ((j 1))
(if (> j rhs-length)
'()
(cons (string->symbol
(string-append
"$"
(number->string j)))
(loop-j (+ j 1)))))))))
(loop1 rest
(+ i 1)
(cons (cons prod action)
rev-productions-and-actions))))))))))
(define (valid-nonterminal? x)
(symbol? x))
(define (valid-terminal? x)
(symbol? x)) ; DB
;; ---------------------------------------------------------------------- ;;
;; Miscellaneous ;;
;; ---------------------------------------------------------------------- ;;
(define (pos-in-list x lst)
(let loop ((lst lst) (i 0))
(cond ((not (pair? lst)) #f)
((equal? (car lst) x) i)
(else (loop (cdr lst) (+ i 1))))))
(define (sunion lst1 lst2) ; union of sorted lists
(let loop ((L1 lst1)
(L2 lst2))
(cond ((null? L1) L2)
((null? L2) L1)
(else
(let ((x (car L1)) (y (car L2)))
(cond
((> x y)
(cons y (loop L1 (cdr L2))))
((< x y)
(cons x (loop (cdr L1) L2)))
(else
(loop (cdr L1) L2))
))))))
(define (sinsert elem lst)
(let loop ((l1 lst))
(if (null? l1)
(cons elem l1)
(let ((x (car l1)))
(cond ((< elem x)
(cons elem l1))
((> elem x)
(cons x (loop (cdr l1))))
(else
l1))))))
(define (lalr-filter p lst)
(let loop ((l lst))
(if (null? l)
'()
(let ((x (car l)) (y (cdr l)))
(if (p x)
(cons x (loop y))
(loop y))))))
;; ---------------------------------------------------------------------- ;;
;; Debugging tools ... ;;
;; ---------------------------------------------------------------------- ;;
(define the-terminals #f) ; names of terminal symbols
(define the-nonterminals #f) ; non-terminals
(define (print-item item-no)
(let loop ((i item-no))
(let ((v (vector-ref ritem i)))
(if (>= v 0)
(loop (+ i 1))
(let* ((rlno (- v))
(nt (vector-ref rlhs rlno)))
(display (vector-ref the-nonterminals nt)) (display " --> ")
(let loop ((i (vector-ref rrhs rlno)))
(let ((v (vector-ref ritem i)))
(if (= i item-no)
(display ". "))
(if (>= v 0)
(begin
(print-symbol v)
(display " ")
(loop (+ i 1)))
(begin
(display " (rule ")
(display (- v))
(display ")")
(newline))))))))))
(define (print-symbol n . port)
(display (if (>= n nvars)
(vector-ref the-terminals (- n nvars))
(vector-ref the-nonterminals n))
(if (null? port)
(current-output-port)
(car port))))
(define (print-states)
"Print the states of a generated parser."
(define (print-action act)
(cond
((eq? act '*error*)
(display " : Error"))
((eq? act 'accept)
(display " : Accept input"))
((< act 0)
(display " : reduce using rule ")
(display (- act)))
(else
(display " : shift and goto state ")
(display act)))
(newline)
#t)
(define (print-actions acts)
(let loop ((l acts))
(if (null? l)
#t
(let ((sym (caar l))
(act (cdar l)))
(display " ")
(cond
((eq? sym 'default)
(display "default action"))
(else
(if (number? sym)
(print-symbol (+ sym nvars))
(display sym))))
(print-action act)
(loop (cdr l))))))
(if (not action-table)
(begin
(display "No generated parser available!")
(newline)
#f)
(begin
(display "State table") (newline)
(display "-----------") (newline) (newline)
(let loop ((l first-state))
(if (null? l)
#t
(let* ((core (car l))
(i (core-number core))
(items (core-items core))
(actions (vector-ref action-table i)))
(display "state ") (display i) (newline)
(newline)
(for-each (lambda (x) (display " ") (print-item x))
items)
(newline)
(print-actions actions)
(newline)
(loop (cdr l))))))))
;; ---------------------------------------------------------------------- ;;
(define build-goto-table
(lambda ()
`(vector
,@(map
(lambda (shifts)
(list 'quote
(if shifts
(let loop ((l (shift-shifts shifts)))
(if (null? l)
'()
(let* ((state (car l))
(symbol (vector-ref acces-symbol state)))
(if (< symbol nvars)
(cons `(,symbol . ,state)
(loop (cdr l)))
(loop (cdr l))))))
'())))
(vector->list shift-table)))))
(define build-reduction-table
(lambda (gram/actions)
`(vector
'()
,@(map
(lambda (p)
(let ((act (cdr p)))
`(lambda (___stack ___sp ___goto-table ___k)
,(let* ((nt (caar p)) (rhs (cdar p)) (n (length rhs)))
`(let* (,@(if act
(let loop ((i 1) (l rhs))
(if (pair? l)
(let ((rest (cdr l)))
(cons
`(,(string->symbol
(string-append
"$"
(number->string
(+ (- n i) 1))))
(vector-ref ___stack (- ___sp ,(- (* i 2) 1))))
(loop (+ i 1) rest)))
'()))
'()))
,(if (= nt 0)
'$1
`(___push ___stack (- ___sp ,(* 2 n))
,nt ___goto-table ,(cdr p) ___k)))))))
gram/actions))))
;; @section (api "API")
(define-macro-with-docs (lalr-parser tokens . rules)
"The grammar declaration special form. @var{tokens} is the list of token
symbols, and @var{rules} are the grammar rules. See the module documentation
for more details."
(let* ((gram/actions (gen-tables! tokens rules))
(code
`(letrec ((___max-stack-size 500)
(___atable ',action-table)
(___gtable ,(build-goto-table))
(___grow-stack (lambda (stack)
;; make a new stack twice as big as the original
(let ((new-stack (make-vector (* 2 (vector-length stack)) #f)))
;; then copy the elements...
(let loop ((i (- (vector-length stack) 1)))
(if (< i 0)
new-stack
(begin
(vector-set! new-stack i (vector-ref stack i))
(loop (- i 1))))))))
(___push (lambda (stack sp new-cat goto-table lval k)
(let* ((state (vector-ref stack sp))
(new-state (cdr (assq new-cat (vector-ref goto-table state))))
(new-sp (+ sp 2))
(stack (if (< new-sp (vector-length stack))
stack
(___grow-stack stack))))
(vector-set! stack new-sp new-state)
(vector-set! stack (- new-sp 1) lval)
(k stack new-sp))))
(___action (lambda (x l)
(let ((y (assq x l)))
(if y (cdr y) (cdar l)))))
(___rtable ,(build-reduction-table gram/actions)))
(lambda (lexerp errorp)
(let ((stack (make-vector ___max-stack-size 0)))
(let loop ((stack stack) (sp 0) (input (lexerp)))
(let* ((state (vector-ref stack sp))
(i (if (pair? input) (car input) input))
(attr (if (pair? input) (cdr input) #f))
(act (___action i (vector-ref ___atable state))))
(if (not (symbol? i))
(errorp "PARSE ERROR: invalid token: " input))
(cond
;; Input succesfully parsed
((eq? act 'accept)
(vector-ref stack 1))
;; Syntax error in input
((eq? act '*error*)
(if (eq? i '*eoi*)
(errorp "PARSE ERROR : unexpected end of input ")
(errorp "PARSE ERROR : unexpected token : " input)))
;; Shift current token on top of the stack
((>= act 0)
(let ((stack (if (< (+ sp 2) (vector-length stack))
stack
(___grow-stack stack))))
(vector-set! stack (+ sp 1) attr)
(vector-set! stack (+ sp 2) act)
(loop stack (+ sp 2) (lexerp))))
;; Reduce by rule (- act)
(else
((vector-ref ___rtable (- act))
stack sp ___gtable
(lambda (stack sp)
(loop stack sp input))))))))))))
code))
;; arch-tag: 4FE771DE-F56D-11D8-8B77-000A95B4C7DC
guile-lib-0.2.6.1/src/container/ 0000775 0000764 0000764 00000000000 13320656103 013312 5 0000000 0000000 guile-lib-0.2.6.1/src/container/nodal-tree.scm 0000664 0000764 0000764 00000004160 13314016560 015771 0000000 0000000 ;; (container nodal-tree) -- a tree data structure
;; Copyright (C) 2003,2004 Andy Wingo
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;; Commentary:
;;
;;A nodal tree is a tree composed of nodes, each of which may have
;;children. Nodes are represented as alists. The only alist entry that
;;is specified is @code{children}, which must hold a list of child
;;nodes. Other entries are intentionally left unspecified, so as to
;;allow for extensibility.
;;
;;; Code:
(define-module (container nodal-tree)
#:export (nodal-tree? make-node
node-ref node-set! node-children))
;; Returns pairs, not lists
(define (group-pairs l)
(let lp ((in l) (out '()))
(cond
((null? in) (reverse! out))
(else (lp (list-cdr-ref in 2) (acons (car in) (cadr in) out))))))
(define (make-node . attributes)
(or (even? (length attributes)) (error "invalid node atrributes"))
(cons 'nodal-tree
(let ((body (group-pairs attributes)))
(if (assq 'children body)
body
(acons 'children '() body)))))
(define (node-set! node name val)
(set-cdr! node (assq-set! (cdr node) name val)))
(define (node-ref node name)
(assq-ref (cdr node) name))
(define (node-children node)
(or (node-ref node 'children) '()))
(define (nodal-tree? x)
"Predicate to determine if @var{x} is a nodal tree. Not particularly
efficient: intended for debugging purposes."
(and (list? x)
(eq? (car x) 'nodal-tree)
(and-map pair? x)
(and-map nodal-tree? (node-children x))))
guile-lib-0.2.6.1/src/container/async-queue.scm 0000664 0000764 0000764 00000004174 13314016560 016203 0000000 0000000 ;; (container async-queue) -- a thread-safe asynchronous queue
;; Copyright (C) 2007 Andreas Rottmann
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;; Commentary:
;;
;;A asynchronous queue can be used to safely send messages from one
;;thread to another.
;;
;;; Code:
(define-module (container async-queue)
#:export (make-async-queue async-enqueue! async-dequeue!)
#:use-module (ice-9 q)
#:use-module (ice-9 threads)
#:use-module (oop goops))
(define-class ()
(queue #:init-form (make-q) #:getter queue)
(condv #:init-form (make-condition-variable) #:getter condv)
(mutex #:init-form (make-mutex) #:getter mutex)
(waiting-threads #:init-value 0 #:accessor waiting-threads))
(define (make-async-queue)
"Create a new asynchronous queue."
(make ))
(define (async-enqueue! q elt)
"Enqueue @var{elt} into @var{q}."
(with-mutex (mutex q)
(enq! (queue q) elt)
(if (> (waiting-threads q) 0)
(signal-condition-variable (condv q)))))
(define (async-dequeue! q)
"Dequeue a single element from @var{q}. If the queue is empty, the
calling thread is blocked until an element is enqueued by another
thread."
(with-mutex (mutex q)
(cond ((q-empty? (queue q))
(set! (waiting-threads q) (+ (waiting-threads q) 1))
(let loop ()
(cond ((q-empty? (queue q))
(wait-condition-variable (condv q) (mutex q))
(loop))))
(set! (waiting-threads q) (- (waiting-threads q) 1))))
(deq! (queue q))))
guile-lib-0.2.6.1/src/container/delay-tree.scm 0000664 0000764 0000764 00000002731 13314016560 015774 0000000 0000000 ;; (container delay-tree) -- a nodal tree with promises
;; Copyright (C) 2003,2004 Andy Wingo
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;; Commentary:
;;
;;A delay tree is a superset of a nodal tree (see (container
;;nodal-tree)). It extends nodal trees to allow any entry of the node to
;;be a promise created with the @code{delay} operator.
;;
;;; Code:
(define-module (container delay-tree)
#:use-module (container nodal-tree)
#:export (force-ref))
(define (force-ref node field)
"Access a field in a node of a delay tree. If the value of the field
is a promise, the promise will be forced, and the value will be replaced
with the forced value."
(let ((val (node-ref node field)))
(and val
(if (promise? val)
(begin
(node-set! node field (force val))
(node-ref node field))
val))))
guile-lib-0.2.6.1/src/md5.scm 0000664 0000764 0000764 00000036371 13314016560 012453 0000000 0000000 ;; (md5) -- md5 hashing in scheme
;; Copyright (C) 2001, 2002, 2003, 2013 Free Software Foundation, Inc.
;; Copyright (C) 2004 Moritz Schulte .
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Lesser General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this program. If not, see
;; .
#!
;;; Commentary:
This code is heavily based on the MD5 implementation contained in
Libgcrypt. To a certain degree this code is a literal translation from
referenced C implementation into Scheme.
;;; Code:
!#
(define-module (md5)
#:use-module (ice-9 rw)
#:export (md5))
;; General helper functions.
(define (buffer->hexstring string)
(define (buffer->hexstring-do string-rest string-new)
(if (string-null? string-rest)
string-new
(let ((byte (char->integer (string-ref string-rest 0))))
(buffer->hexstring-do
(substring string-rest 1)
(string-append string-new
(number->string (logand (ash byte -4) #xF) 16)
(number->string (logand (ash byte -0) #xF) 16))))))
(buffer->hexstring-do string ""))
(define (buffer->word buffer)
(logior (ash (char->integer (string-ref buffer 0)) 0)
(ash (char->integer (string-ref buffer 1)) 8)
(ash (char->integer (string-ref buffer 2)) 16)
(ash (char->integer (string-ref buffer 3)) 24)))
(define (buffer->words buffer n)
(define (buffer->words-do buffer i words)
(if (= i n)
words
(buffer->words-do (substring buffer 4)
(+ i 1)
(append words
`(,(buffer->word (substring buffer 0 4)))))))
(buffer->words-do buffer 0 `()))
(define (word->buffer word)
(let ((buffer (make-string 4 #\nul)))
(string-set! buffer 0 (integer->char (logand (ash word -0) #xFF)))
(string-set! buffer 1 (integer->char (logand (ash word -8) #xFF)))
(string-set! buffer 2 (integer->char (logand (ash word -16) #xFF)))
(string-set! buffer 3 (integer->char (logand (ash word -24) #xFF)))
buffer))
;; Some math basics.
(define f-add +)
(define f-ash ash)
(define (+ . args)
(modulo (apply f-add args) #x100000000))
(define (ash x n)
(modulo (f-ash x n) #x100000000))
(define (rol x n)
(logior (ash x n)
(ash x (- (- 32 n)))))
;; Return a new, initialized MD5 context.
(define (md5-init)
(let ((buffer-space (make-string 64 #\nul)))
;; Since this is a mutable state, cons it up
(list
(cons 'values (list (cons 'a #x67452301)
(cons 'b #xEFCDAB89)
(cons 'c #x98BADCFE)
(cons 'd #x10325476)))
(cons 'buffer (list (cons 'space buffer-space)
(cons 'data-size 0)))
(cons 'stats (list (cons 'blocks-processed 0))))))
(define (md5-func-f b c d)
(logior (logand b c) (logand (lognot b) d)))
(define (md5-func-g b c d)
(logior (logand d b) (logand (lognot d) c)))
(define (md5-func-h b c d)
(logxor b c d))
(define (md5-func-i b c d)
(logxor c (logior b (lognot d))))
(define-macro (md5-transform-op-round1 a b c d s T)
`(begin
(set! ,a (+ ,a (md5-func-f ,b ,c ,d) (list-ref words word-idx) ,T))
(set! word-idx (+ word-idx 1))
(set! ,a (rol ,a ,s))
(set! ,a (+ ,a ,b))))
(define-macro (md5-transform-op-round2/3/4 f a b c d k s T)
`(begin
(set! ,a (+ ,a (,f ,b ,c ,d) (list-ref words ,k) ,T))
(set! ,a (rol ,a ,s))
(set! ,a (+ ,a ,b))))
(define (md5-transform-block context data)
(let ((words (buffer->words data 16))
(word-idx 0)
(a (assq-ref (assq-ref context 'values) 'a))
(b (assq-ref (assq-ref context 'values) 'b))
(c (assq-ref (assq-ref context 'values) 'c))
(d (assq-ref (assq-ref context 'values) 'd)))
;; Round 1.
(md5-transform-op-round1 a b c d 7 #xD76AA478)
(md5-transform-op-round1 d a b c 12 #xE8C7B756)
(md5-transform-op-round1 c d a b 17 #x242070DB)
(md5-transform-op-round1 b c d a 22 #xC1BDCEEE)
(md5-transform-op-round1 a b c d 7 #xF57C0FAF)
(md5-transform-op-round1 d a b c 12 #x4787C62A)
(md5-transform-op-round1 c d a b 17 #xA8304613)
(md5-transform-op-round1 b c d a 22 #xFD469501)
(md5-transform-op-round1 a b c d 7 #x698098D8)
(md5-transform-op-round1 d a b c 12 #x8B44F7AF)
(md5-transform-op-round1 c d a b 17 #xFFFF5BB1)
(md5-transform-op-round1 b c d a 22 #x895CD7BE)
(md5-transform-op-round1 a b c d 7 #x6B901122)
(md5-transform-op-round1 d a b c 12 #xFD987193)
(md5-transform-op-round1 c d a b 17 #xA679438E)
(md5-transform-op-round1 b c d a 22 #x49B40821)
;; Round 2.
(md5-transform-op-round2/3/4 md5-func-g a b c d 1 5 #xF61E2562)
(md5-transform-op-round2/3/4 md5-func-g d a b c 6 9 #xC040B340)
(md5-transform-op-round2/3/4 md5-func-g c d a b 11 14 #x265E5A51)
(md5-transform-op-round2/3/4 md5-func-g b c d a 0 20 #xE9B6C7AA)
(md5-transform-op-round2/3/4 md5-func-g a b c d 5 5 #xD62F105D)
(md5-transform-op-round2/3/4 md5-func-g d a b c 10 9 #x02441453)
(md5-transform-op-round2/3/4 md5-func-g c d a b 15 14 #xD8A1E681)
(md5-transform-op-round2/3/4 md5-func-g b c d a 4 20 #xE7D3FBC8)
(md5-transform-op-round2/3/4 md5-func-g a b c d 9 5 #x21E1CDE6)
(md5-transform-op-round2/3/4 md5-func-g d a b c 14 9 #xC33707D6)
(md5-transform-op-round2/3/4 md5-func-g c d a b 3 14 #xF4D50D87)
(md5-transform-op-round2/3/4 md5-func-g b c d a 8 20 #x455A14ED)
(md5-transform-op-round2/3/4 md5-func-g a b c d 13 5 #xA9E3E905)
(md5-transform-op-round2/3/4 md5-func-g d a b c 2 9 #xFCEFA3F8)
(md5-transform-op-round2/3/4 md5-func-g c d a b 7 14 #x676F02D9)
(md5-transform-op-round2/3/4 md5-func-g b c d a 12 20 #x8D2A4C8A)
;; Round 3.
(md5-transform-op-round2/3/4 md5-func-h a b c d 5 4 #xFFFA3942)
(md5-transform-op-round2/3/4 md5-func-h d a b c 8 11 #x8771F681)
(md5-transform-op-round2/3/4 md5-func-h c d a b 11 16 #x6D9D6122)
(md5-transform-op-round2/3/4 md5-func-h b c d a 14 23 #xFDE5380C)
(md5-transform-op-round2/3/4 md5-func-h a b c d 1 4 #xA4BEEA44)
(md5-transform-op-round2/3/4 md5-func-h d a b c 4 11 #x4BDECFA9)
(md5-transform-op-round2/3/4 md5-func-h c d a b 7 16 #xF6BB4B60)
(md5-transform-op-round2/3/4 md5-func-h b c d a 10 23 #xBEBFBC70)
(md5-transform-op-round2/3/4 md5-func-h a b c d 13 4 #x289B7EC6)
(md5-transform-op-round2/3/4 md5-func-h d a b c 0 11 #xEAA127FA)
(md5-transform-op-round2/3/4 md5-func-h c d a b 3 16 #xD4EF3085)
(md5-transform-op-round2/3/4 md5-func-h b c d a 6 23 #x04881D05)
(md5-transform-op-round2/3/4 md5-func-h a b c d 9 4 #xD9D4D039)
(md5-transform-op-round2/3/4 md5-func-h d a b c 12 11 #xE6DB99E5)
(md5-transform-op-round2/3/4 md5-func-h c d a b 15 16 #x1FA27CF8)
(md5-transform-op-round2/3/4 md5-func-h b c d a 2 23 #xC4AC5665)
;; Round 4.
(md5-transform-op-round2/3/4 md5-func-i a b c d 0 6 #xF4292244)
(md5-transform-op-round2/3/4 md5-func-i d a b c 7 10 #x432AFF97)
(md5-transform-op-round2/3/4 md5-func-i c d a b 14 15 #xAB9423A7)
(md5-transform-op-round2/3/4 md5-func-i b c d a 5 21 #xFC93A039)
(md5-transform-op-round2/3/4 md5-func-i a b c d 12 6 #x655B59C3)
(md5-transform-op-round2/3/4 md5-func-i d a b c 3 10 #x8F0CCC92)
(md5-transform-op-round2/3/4 md5-func-i c d a b 10 15 #xFFEFF47D)
(md5-transform-op-round2/3/4 md5-func-i b c d a 1 21 #x85845DD1)
(md5-transform-op-round2/3/4 md5-func-i a b c d 8 6 #x6FA87E4F)
(md5-transform-op-round2/3/4 md5-func-i d a b c 15 10 #xFE2CE6E0)
(md5-transform-op-round2/3/4 md5-func-i c d a b 6 15 #xA3014314)
(md5-transform-op-round2/3/4 md5-func-i b c d a 13 21 #x4E0811A1)
(md5-transform-op-round2/3/4 md5-func-i a b c d 4 6 #xF7537E82)
(md5-transform-op-round2/3/4 md5-func-i d a b c 11 10 #xBD3AF235)
(md5-transform-op-round2/3/4 md5-func-i c d a b 2 15 #x2AD7D2BB)
(md5-transform-op-round2/3/4 md5-func-i b c d a 9 21 #xEB86D391)
(assq-set! (assq-ref context 'values)
'a
(+ (assq-ref (assq-ref context 'values) 'a)
a))
(assq-set! (assq-ref context 'values)
'b
(+ (assq-ref (assq-ref context 'values) 'b)
b))
(assq-set! (assq-ref context 'values)
'c
(+ (assq-ref (assq-ref context 'values) 'c)
c))
(assq-set! (assq-ref context 'values)
'd
(+ (assq-ref (assq-ref context 'values) 'd)
d))))
(define (md5-write-do context data data-size)
(if (= (assq-ref (assq-ref context 'buffer) 'data-size) 64)
;; Flush the buffer.
(begin
(md5-transform-block context (assq-ref (assq-ref context 'buffer)
'space))
(assq-set! (assq-ref context 'buffer) 'data-size 0)
(assq-set! (assq-ref context 'stats)
'blocks-processed
(+ (assq-ref (assq-ref context 'stats) 'blocks-processed)
1))))
(if (> data-size 0)
(begin
(if (> (assq-ref (assq-ref context 'buffer)
'data-size)
0)
;; Fill buffer.
(while (and (> data-size
0)
(< (assq-ref (assq-ref context 'buffer)
'data-size)
64))
(begin
(string-set! (assq-ref (assq-ref context 'buffer)
'space)
(assq-ref (assq-ref context 'buffer)
'data-size)
(string-ref data 0))
(assq-set! (assq-ref context 'buffer)
'data-size
(+ (assq-ref (assq-ref context 'buffer)
'data-size)
1))
(set! data (substring data 1))
(set! data-size (- data-size 1)))))
;; Transform whole blocks.
(while (>= data-size 64)
(begin
(md5-transform-block context data)
(assq-set! (assq-ref context 'stats)
'blocks-processed
(+ (assq-ref (assq-ref context 'stats)
'blocks-processed)
1))
(set! data-size (- data-size 64))
(set! data (substring data 64))))
;; Fill buffer.
(while (and (> data-size
0)
(< (assq-ref (assq-ref context 'buffer)
'data-size)
64))
(begin
(string-set! (assq-ref (assq-ref context 'buffer)
'space)
(assq-ref (assq-ref context 'buffer)
'data-size)
(string-ref data 0))
(assq-set! (assq-ref context 'buffer)
'data-size
(+ (assq-ref (assq-ref context 'buffer)
'data-size)
1))
(set! data-size (- data-size 1))
(set! data (substring data 1)))))))
;; Write data to context.
(define (md5-write context data data-size)
(md5-write-do context data data-size))
;; Finalize context, return hash.
(define (md5-finalize context)
(let ((t 0)
(msb 0)
(lsb 0))
(md5-write-do context "" 0)
(set! t (assq-ref (assq-ref context 'stats)
'blocks-processed))
(set! lsb (ash t 6))
(set! msb (ash t -26))
(set! t lsb)
(set! lsb (+ lsb (assq-ref (assq-ref context 'buffer)
'data-size)))
(if (< lsb t)
(set! msb (+ msb 1)))
(set! t lsb)
(set! lsb (ash lsb 3))
(set! msb (ash msb 3))
(set! msb (logior msb (ash t -29)))
(if (< (assq-ref (assq-ref context 'buffer) 'data-size) 56)
(begin
(string-set! (assq-ref (assq-ref context 'buffer)
'space)
(assq-ref (assq-ref context 'buffer)
'data-size)
(integer->char #x80))
(assq-set! (assq-ref context 'buffer)
'data-size
(+ (assq-ref (assq-ref context 'buffer)
'data-size)
1))
(while (< (assq-ref (assq-ref context 'buffer)
'data-size)
56)
(begin
(string-set! (assq-ref (assq-ref context 'buffer)
'space)
(assq-ref (assq-ref context 'buffer)
'data-size)
#\nul)
(assq-set! (assq-ref context 'buffer)
'data-size
(+ (assq-ref (assq-ref context 'buffer)
'data-size)
1)))))
(begin
(string-set! (assq-ref (assq-ref context 'buffer)
'space)
(assq-ref (assq-ref context 'buffer)
'data-size)
(integer->char #x80))
(assq-set! (assq-ref context 'buffer)
'data-size
(+ (assq-ref (assq-ref context 'buffer)
'data-size)
1))
(while (< (assq-ref (assq-ref context 'buffer)
'data-size)
64)
(begin
(string-set! (assq-ref (assq-ref context 'buffer)
'space)
(assq-ref (assq-ref context 'buffer)
'data-size)
#\nul)
(assq-set! (assq-ref context 'buffer)
'data-size
(+ (assq-ref (assq-ref context 'buffer)
'data-size)
1))))
(md5-write-do context "" 0)
(substring-fill! (assq-ref (assq-ref context 'buffer)
'space)
0
56
#\nul)))
(let ((final-string (map (lambda (x)
(integer->char (logand x #xFF)))
`(,lsb
,(ash lsb -8)
,(ash lsb -16)
,(ash lsb -24)
,msb
,(ash msb -8)
,(ash msb -16)
,(ash msb -24))))
(buffer (assq-ref (assq-ref context 'buffer) 'space)))
(string-set! buffer 56 (list-ref final-string 0))
(string-set! buffer 57 (list-ref final-string 1))
(string-set! buffer 58 (list-ref final-string 2))
(string-set! buffer 59 (list-ref final-string 3))
(string-set! buffer 60 (list-ref final-string 4))
(string-set! buffer 61 (list-ref final-string 5))
(string-set! buffer 62 (list-ref final-string 6))
(string-set! buffer 63 (list-ref final-string 7)))
(md5-transform-block context (assq-ref (assq-ref context 'buffer)
'space))
(buffer->hexstring
(string-append (word->buffer (assq-ref (assq-ref context 'values) 'a))
(word->buffer (assq-ref (assq-ref context 'values) 'b))
(word->buffer (assq-ref (assq-ref context 'values) 'c))
(word->buffer (assq-ref (assq-ref context 'values) 'd))))))
(define (general-read-string!/partial buffer port)
(if (file-port? port)
(read-string!/partial buffer port)
(let ((max-index (- (string-length buffer) 1)))
(let loop ((ch (read-char port))
(read 0))
(if (eof-object? ch)
(if (= read 0)
#f
read)
(begin
(string-set! buffer read ch)
(if (< read max-index)
(loop (read-char port) (+ read 1))
(+ read 1))))))))
(define (md5 . port)
"Reads data from @var{port}, and returns a string containing the calculated
md5 hash of the data. If @var{port} is not given, then the default input
port is used."
(define (process-data buffer port callback arg)
(define (process-data-do)
(let ((bytes-read (general-read-string!/partial buffer port)))
(if (not bytes-read)
#t
(begin
(callback arg buffer bytes-read)
(process-data-do)))))
(process-data-do))
(define (process-data-callback arg data data-size)
(md5-write arg data data-size))
(if (null? port)
(set! port (current-input-port))
(set! port (car port)))
(let* ((context (md5-init))
(buffer-size 4096)
(buffer (make-string buffer-size #\nul)))
(process-data buffer port process-data-callback context)
(md5-finalize context)))
;; arch-tag: 03A57FCF-F9E7-11D8-A6BC-000A95CD5044
guile-lib-0.2.6.1/src/debugging/ 0000775 0000764 0000764 00000000000 13320656103 013263 5 0000000 0000000 guile-lib-0.2.6.1/src/debugging/assert.scm 0000664 0000764 0000764 00000014160 13314016560 015212 0000000 0000000 ;; guile-lib
;; Written 2001 by Oleg Kiselyov .
;; Modified 2004 by Andy Wingo .
;; This file is based on SSAX's myenv-scm.scm, and is in the public
;; domain.
;;; Commentary:
;;
;; Defines an @code{assert} macro, and the @code{cout} and @code{cerr}
;; utility functions.
;;
;;; Code:
(define-module (debugging assert)
#:use-module (scheme documentation)
#:export (assert cout cerr))
(define (cout . args)
"Similar to @code{cout << arguments << args}, where @var{argument} can
be any Scheme object. If it's a procedure (e.g. @code{newline}), it's
called without args rather than printed."
(for-each (lambda (x)
(if (procedure? x) (x) (display x)))
args))
(define (cerr . args)
"Similar to @code{cerr << arguments << args}, where @var{argument} can
be any Scheme object. If it's a procedure (e.g. @code{newline}), it's
called without args rather than printed."
(for-each (lambda (x)
(if (procedure? x) (x (current-error-port)) (display x (current-error-port))))
args))
(define nl (string #\newline))
(cond-expand
(guile-2
(define-syntax assert
(syntax-rules (report:)
"Assert the truth of an expression (or of a sequence of expressions).
syntax: @code{assert @var{?expr} @var{?expr} ... [report: @var{?r-exp} @var{?r-exp} ...]}
If @code{(and @var{?expr} @var{?expr} ...)} evaluates to anything but
@code{#f}, the result is the value of that expression. Otherwise, an
error is reported.
The error message will show the failed expressions, as well as the
values of selected variables (or expressions, in general). The user may
explicitly specify the expressions whose values are to be printed upon
assertion failure -- as @var{?r-exp} that follow the identifier
@code{report:}.
Typically, @var{?r-exp} is either a variable or a string constant. If
the user specified no @var{?r-exp}, the values of variables that are
referenced in @var{?expr} will be printed upon the assertion failure."
((assert "doit" (expr ...) (r-exp ...))
(cond
((and expr ...) => (lambda (x) x))
(else
(error "assertion failure: ~a" (list '(and expr ...) r-exp ...)))))
((assert "collect" (expr ...))
(assert "doit" (expr ...) ()))
((assert "collect" (expr ...) report: r-exp ...)
(assert "doit" (expr ...) (r-exp ...)))
((assert "collect" (expr ...) expr1 stuff ...)
(assert "collect" (expr ... expr1) stuff ...))
((assert stuff ...)
(assert "collect" () stuff ...)))))
(else
(define-macro-with-docs (assert expr . others)
"Assert the truth of an expression (or of a sequence of expressions).
syntax: @code{assert @var{?expr} @var{?expr} ... [report: @var{?r-exp} @var{?r-exp} ...]}
If @code{(and @var{?expr} @var{?expr} ...)} evaluates to anything but
@code{#f}, the result is the value of that expression. Otherwise, an
error is reported.
The error message will show the failed expressions, as well as the
values of selected variables (or expressions, in general). The user may
explicitly specify the expressions whose values are to be printed upon
assertion failure -- as @var{?r-exp} that follow the identifier
@code{report:}.
Typically, @var{?r-exp} is either a variable or a string constant. If
the user specified no @var{?r-exp}, the values of variables that are
referenced in @var{?expr} will be printed upon the assertion failure."
;; given the list of expressions or vars, make the list appropriate
;; for cerr
(define (make-print-list prefix lst)
(cond
((null? lst) '())
((symbol? (car lst))
(cons #\newline
(cons (list 'quote (car lst))
(cons ": " (cons (car lst) (make-print-list #\newline (cdr lst)))))))
(else
(cons prefix (cons (car lst) (make-print-list "" (cdr lst)))))))
;; return the list of all unique "interesting" variables in the expr.
;; Variables that are certain to be bound to procedures are not
;; interesting.
(define (vars-of expr)
(let loop ((expr expr) (vars '()))
(cond
((not (pair? expr)) vars) ; not an application -- ignore
((memq (car expr)
'(quote let let* letrec let-values* lambda cond quasiquote
case define do assert))
vars) ; won't go there
(else ; ignore the head of the application
(let inner ((expr (cdr expr)) (vars vars))
(cond
((null? expr) vars)
((symbol? (car expr))
(inner (cdr expr)
(if (memq (car expr) vars) vars (cons (car expr) vars))))
(else
(inner (cdr expr) (loop (car expr) vars)))))))))
(cond
((null? others) ; the most common case
`(or ,expr (begin (cerr "failed assertion: " ',expr ,nl "bindings"
,@(make-print-list #\newline (vars-of expr)) ,nl)
(error "assertion failure"))))
((eq? (car others) 'report:) ; another common case
`(or ,expr (begin (cerr "failed assertion: " ',expr
,@(make-print-list #\newline (cdr others)) ,nl)
(error "assertion failure"))))
((not (memq 'report: others))
`(or (and ,expr ,@others)
(begin (cerr "failed assertion: " '(,expr ,@others) ,nl "bindings"
,@(make-print-list #\newline
(vars-of (cons 'and (cons expr others)))) ,nl)
(error "assertion failure"))))
(else ; report: occurs somewhere in 'others'
(let loop ((exprs (list expr)) (reported others))
(cond
((eq? (car reported) 'report:)
`(or (and ,@(reverse exprs))
(begin (cerr "failed assertion: " ',(reverse exprs)
,@(make-print-list #\newline (cdr reported)) ,nl)
(error "assertion failure"))))
(else (loop (cons (car reported) exprs) (cdr reported))))))))))
;;; arch-tag: e3b450e8-1af2-4f09-a36e-e4dd48fc363c
;;; assert.scm ends here
guile-lib-0.2.6.1/src/debugging/time.scm 0000664 0000764 0000764 00000003116 13314016560 014646 0000000 0000000 ;; (debugging time) -- a timing macro
;; Copyright (C) 2004 Andy Wingo
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
;;; Commentary:
;;
;;@c it's really texinfo
;; Defines a macro to time execution of a body of expressions. Each
;; element is timed individually.
;;
;;; Code:
(define-module (debugging time)
#:use-module (scheme documentation)
#:export (time))
(define-macro-with-docs (time expr . others)
"syntax: @code{(time @var{expr1} @var{expr2}...)}
Times the execution of a list of expressions, in milliseconds. The
resolution is limited to guile's @code{internal-time-units-per-second}.
Disregards the expressions' return value(s) (FIXME)."
(let ((x (gensym)))
`(let ((,x (get-internal-run-time)))
,expr
(format #t "~A ms\n" (* 1000 (/ (- (get-internal-run-time) ,x)
internal-time-units-per-second)))
,@(if (null? others) '() `((time ,@others))))))
;;; arch-tag: ff9cb210-9d1b-4ad4-aa5d-27a23edc91f2
guile-lib-0.2.6.1/src/logging/ 0000775 0000764 0000764 00000000000 13320656103 012756 5 0000000 0000000 guile-lib-0.2.6.1/src/logging/rotating-log.scm 0000664 0000764 0000764 00000007474 13314016560 016024 0000000 0000000 ;; (logging rotating-log) -- a log that writes to rotating files
;; Copyright (C) 2003 Richard Todd
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
#!
;;; Commentary:
@cindex logs, rotating
This module defines a log handler for text logs that rotate when they
get to be a user-defined size. This is similar to the behavior of
many UNIX standard log files. @xref{logging logger}, for more
information in general on log handlers.
;;; Code:
!#
(define-module (logging rotating-log)
#:use-module (oop goops)
#:use-module (logging logger)
#:use-module (scheme documentation)
#:export ())
(define-class-with-docs ()
"This is a log handler which writes text logs that rotate when they reach
a configurable size limit.
Keywords recognized by @code{} on creation are:
@table @code
@item #:num-files
This is the number of log files you want the logger to use. Default is 4.
@item #:size-limit
This is the size, in bytes, a log file must get before the logs get
rotated. Default is 1MB (104876 bytes).
@item #:file-name
This is the base of the log file name. Default is ``logfile''. Numbers will
be appended to the file name representing the log number. The newest log
file is always ``@var{NAME}.1''.
@item #:formatter
Allows the user to provide a function to use as the log formatter for
this handler. @xref{logging logger }, for details.
@end table
Example of creating a @code{}:
@lisp
(make
#:num-files 3
#:size-limit 1024
#:file-name \"test-log-file\"))
@end lisp"
(num-files #:init-value 4 #:getter num-files #:init-keyword #:num-files)
(size-limit #:init-value 1048576 #:getter size-limit #:init-keyword #:size-limit)
(file-name #:init-value "logfile" #:getter file-name #:init-keyword #:file-name)
(port #:init-value #f #:accessor port)
(fpos #:accessor fpos))
(define-method (log-file-name (self ) num)
(string-append (file-name self) "." (number->string num)))
(define-method (open-log! (self ))
(set! (port self) (open-file (log-file-name self 1) "a"))
(set! (fpos self) (ftell (port self))))
(define-method (close-log! (self ))
(if (port self)
(close-port (port self)))
(set! (port self) #f))
(define-method (rotate-log (self ))
;; close the existing log...
(close-log! self)
;; loop through the files, renaming .2 to .3, .1 to .2, etc...
(let loop ((num (- (num-files self) 1)))
(if (<= num 0)
#t
(begin
(if (access? (log-file-name self num) F_OK)
(rename-file (log-file-name self num)
(log-file-name self (+ num 1))))
(loop (- num 1)))))
;; now open up a new xx.1 file...
(open-log! self))
(define-method (emit-log (self ) str)
(if (port self)
(begin
(display str (port self))
(set! (fpos self) (+ (string-length str)
(fpos self)))
(if (> (fpos self)
(size-limit self))
(rotate-log self)))))
(define-method (flush-log (self ))
(if (port self)
(force-output (port self))))
;;; arch-tag: 2e4d28d0-2c71-4ea5-b46d-572e1aa94a22
guile-lib-0.2.6.1/src/logging/port-log.scm 0000664 0000764 0000764 00000004400 13314016560 015143 0000000 0000000 ;; (logging port-log) -- a log that writes to a given port
;; Copyright (C) 2003 Richard Todd
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
#!
;;; Commentary:
@cindex logs, through ports
@cindex ports, for logging
This module defines a log handler that writes to an arbitrary port of
the user's choice. Uses of this handler could include:
@itemize @bullet
@item
Sending logs across a socket to a network log collector.
@item
Sending logs to the screen
@item
Sending logs to a file
@item
Collecting logs in memory in a string port for later use
@end itemize
;;; Code:
!#
(define-module (logging port-log)
#:use-module (oop goops)
#:use-module (logging logger)
#:use-module (scheme documentation)
#:export ())
(define-class-with-docs ()
"This is a log handler which writes logs to a user-provided port.
Keywords recognized by @code{} on creation are:
@table @code
@item #:port
This is the port to which the log handler will write.
@item #:formatter
Allows the user to provide a function to use as the log formatter for
this handler. @xref{logging logger }, for details.
@end table
Example of creating a @code{}:
@lisp
(make #:port (current-error-port))
@end lisp"
(port #:init-value #f #:accessor port #:init-keyword #:port))
(define-method (emit-log (self ) str)
(if (port self)
(display str (port self))))
(define-method (flush-log (self ))
(if (port self)
(force-output (port self))))
(define-method (close-log! (self ))
(if (port self)
(close-port (port self)))
(set! (port self) #f))
;;; arch-tag: 41c3aa9e-3a3f-41a0-be3c-a28995989634
guile-lib-0.2.6.1/src/logging/logger.scm 0000664 0000764 0000764 00000037530 13314016560 014671 0000000 0000000 ;; (logging logger) -- write methods to log files
;; Copyright (C) 2003 Richard Todd
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see .
#!
;;; Commentary:
@cindex logging
@cindex loggers, relationship with handlers
@cindex handlers, relationship with loggers
@cindex log levels
This is a logging subsystem similar to the one in the python standard
library. There are two main concepts to understand when working with
the logging modules. These are loggers and log handlers.
@table @asis
@item Loggers
Loggers are the front end interfaces for program logging.
They can be registered by name so that no part of a program
needs to be concerned with passing around loggers. In
addition, a default logger can be designated so that, for
most applications, the program does not need to be concerned
with logger instances at all beyond the initial setup.
Log messages all flow through a logger. Messages carry with them a
level (for example: 'WARNING, 'ERROR, 'CRITICAL), and loggers can
filter out messages on a level basis at runtime. This way, the amount
of logging can be turned up during development and bug investigation,
but turned back down on stable releases.
Loggers depend on Log Handlers to actually get text to the log's
destination (for example, a disk file). A single Logger can send
messages through multiple Log Handlers, effectively multicasting logs
to multiple destinations.
@item Log Handlers
Log Handlers actually route text to a destination. One or more handlers
must be attached to a logger for any text to actually appear in a log.
Handlers apply a configurable transformation to the text so that it is
formatted properly for the destination (for instance: syslogs, or a
text file). Like the loggers, they can filter out messages based on
log levels. By using filters on both the Logger and the Handlers,
precise controls can be put on which log messages go where, even
within a single logger.
@end table
@section Example use of logger
Here is an example program that sets up a logger with two handlers. One
handler sends the log messages to a text log that rotates its logs. The
other handler sends logs to standard error, and has its levels set so that
INFO and WARN-level logs don't get through.
@lisp
(use-modules (logging logger)
(logging rotating-log)
(logging port-log)
(scheme documentation)
(oop goops))
;; ----------------------------------------------------------------------
;; Support functions
;; ----------------------------------------------------------------------
(define (setup-logging)
(let ((lgr (make ))
(rotating (make
#:num-files 3
#:size-limit 1024
#:file-name "test-log-file"))
(err (make #:port (current-error-port))))
;; don't want to see warnings or info on the screen!!
(disable-log-level! err 'WARN)
(disable-log-level! err 'INFO)
;; add the handlers to our logger
(add-handler! lgr rotating)
(add-handler! lgr err)
;; make this the application's default logger
(set-default-logger! lgr)
(open-log! lgr)))
(define (shutdown-logging)
(flush-log) ;; since no args, it uses the default
(close-log!) ;; since no args, it uses the default
(set-default-logger! #f))
;; ----------------------------------------------------------------------
;; Main code
;; ----------------------------------------------------------------------
(setup-logging)
;; Due to log levels, this will get to file,
;; but not to stderr
(log-msg 'WARN "This is a warning.")
;; This will get to file AND stderr
(log-msg 'CRITICAL "ERROR message!!!")
(shutdown-logging)
@end lisp
;;; Code:
!#
(define-module (logging logger)
#:export (
;; handler exports...
emit-log
accept-log
;; logger exports...
add-handler!
log-msg
;; module-level methods...
set-default-logger!
register-logger!
lookup-logger
;; these work on loggers and handlers...
enable-log-level!
disable-log-level!
flush-log
open-log!
close-log!
)
#:use-module (oop goops)
#:use-module (scheme documentation))
;;; ----------------------------------------------------------------------
(define default-logger #f)
(define all-loggers (make-hash-table 7))
(define (set-default-logger! lgr)
"Sets the given logger, @var{lgr}, as the default for logging methods where
a logger is not given. @var{lgr} can be an instance of @code{},
a string that has been registered via @code{register-logger!}, or @code{#f}
to remove the default logger.
With this mechanism, most applications will never need to worry about
logger registration or lookup.
@lisp
;; example 1
(set-default-logger! \"main\") ;; look up \"main\" logger and make it the default
;; example 2
(define lgr (make ))
(add-handler! lgr
(make
#:port (current-error-port)))
(set-default-logger! lgr)
(log-msg 'CRITICAL \"This is a message to the default logger!!!\")
(log-msg lgr 'CRITICAL \"This is a message to a specific logger!!!\")
@end lisp"
(cond ((string? lgr)
(set! default-logger (hash-ref all-loggers lgr)))
((is-a? lgr ) (set! default-logger lgr))
((not lgr) (set! default-logger #f))
(else (throw 'bad-type "expected a string, #f, or a "))))
(define (register-logger! str lgr)
"Makes @var{lgr} accessible from other parts of the program by a name
given in @var{str}. @var{str} should be a string, and @var{lgr}
should be an instance of class @code{}.
@lisp
(define main-log (make ))
(define corba-log (make ))
(register-logger! \"main\" main-log)
(register-logger! \"corba\" corba-log)
;; in a completely different part of the program....
(log-msg (lookup-logger \"corba\") 'WARNING \"This is a corba warning.\")
@end lisp"
(if (not (string? str))
(throw 'bad-type "Expected a string for the log registration"))
(hash-set! all-loggers str lgr))
(define (lookup-logger str)
"Looks up an instance of class @code{} by the name given
in @var{str}. The string should have already been registered via
a call to @code{register-logger!}."
(if (not (string? str))
(throw 'bad-type "Expected a string for the logger lookup"))
(hash-ref all-loggers str))
(define-class-with-docs ()
"This is the class that aggregates and manages log handlers. It also
maintains the global information about which levels of log messages
are enabled, and which have been suppressed. Keyword arguments accepted
on creation are:
@table @code
@item #:handlers
This optional parameter must be a list of objects derived from @code{}.
Handlers can always be added later via @code{add-handler!} calls.
@end table"
(levels #:init-form (make-hash-table 17) #:getter levels)
(log-handlers #:init-value '() #:accessor handlers #:init-keyword #:handlers))
(define (log-helper lgr level objs)
;; the level must be enabled in the logger to proceed...
(if (level-enabled? lgr level)
(let ((cur-time (current-time)))
(for-each (lambda (str)
(if (not (string-null? str))
;; pass the string to each log handler for lgr
(for-each (lambda (handler)
(accept-log handler level cur-time str))
(handlers lgr))))
;; split the string at newlines into different log statements
(string-split
(with-output-to-string (lambda () (for-each (lambda (o) (display o)) objs)))
#\nl)))))
(define-generic-with-docs log-msg
"@code{log-msg [lgr] lvl arg1 arg2 ...}. Send a log message
made up of the @code{display}'ed representation of the given
arguments. The log is generated at level @var{lvl}, which should
be a symbol. If the @var{lvl} is disabled, the log message is
not generated. Generated log messages are sent through each of
@var{lgr}'s handlers.
If the @var{lgr} parameter is omitted, then the default logger
is used, if one is set.
As the args are @code{display}'ed, a large string is built up. Then,
the string is split at newlines and sent through the log handlers as
independent log messages. The reason for this behavior is to make
output nicer for log handlers that prepend information like pid and
timestamps to log statements.
@lisp
;; logging to default logger, level of WARN
(log-msg 'WARN \"Warning! \" x \" is bigger than \" y \"!!!\")
;; looking up a logger and logging to it
(let ((l (lookup-logger \"main\")))
(log-msg l 'CRITICAL \"FAILURE TO COMMUNICATE!\")
(log-msg l 'CRITICAL \"ABORTING NOW\"))
@end lisp")
(define-method (log-msg (lvl ) . objs)
(if default-logger
(log-helper default-logger lvl objs)))
(define-method (log-msg (lgr ) lvl . objs)
(log-helper lgr lvl objs))
;; the default formatter makes a log statement like:
;; 2003/12/29 14:53:02 (CRITICAL): The servers are melting!
(define (default-log-formatter lvl time str)
(with-output-to-string
(lambda ()
(display (strftime "%F %H:%M:%S" (localtime time)))
(display " (")
(display (symbol->string lvl))
(display "): ")
(display str)
(newline))))
(define-class-with-docs ()
"This is the base class for all of the log handlers, and encompasses
the basic functionality that all handlers are expected to have.
Keyword arguments recognized by the @code{} at creation
time are:
@table @code
@item #:formatter
This optional parameter must be a function that takes three arguments:
the log level, the time (as from @code{current-time}), and the log string
itself. The function must return a string representing the formatted log.
Here is an example invokation of the default formatter, and what it's
output looks like:
@lisp
(default-log-formatter 'CRITICAL
(current-time)
\"The servers are melting!\")
==> \"2003/12/29 14:53:02 (CRITICAL): The servers are melting!\"
@end lisp
@end table"
(formatter #:init-value default-log-formatter #:getter log-formatter #:init-keyword #:formatter)
(levels #:init-form (make-hash-table 17) #:getter levels))
(define-generic-with-docs add-handler!
"@code{add-handler! lgr handler}. Adds @var{handler} to @var{lgr}'s list of handlers. All subsequent
logs will be sent through the new handler, as well as any previously
registered handlers.")
(define-method (add-handler! (lgr ) (handler ))
(set! (handlers lgr)
(cons handler (handlers lgr))))
(define-generic-with-docs accept-log
"@code{accept-log handler lvl time str}. If @var{lvl} is
enabled for @var{handler}, then @var{str} will be formatted and
sent to the log via the @code{emit-log} method. Formatting is
done via the formatting function given at @var{handler}'s
creation time, or by the default if none was given.
This method should not normally need to be overridden by subclasses.
This method should not normally be called by users of the logging
system. It is only exported so that writers of log handlers can
override this behavior.")
;; This can be overridden by log handlers if this default behaviour
;; is not desired..
(define-method (accept-log (self ) level time str)
(if (level-enabled? self level)
(emit-log self ((log-formatter self) level time str))))
;; This should be overridden by all log handlers to actually
;; write out a string.
(define-generic-with-docs emit-log
"@code{emit-log handler str}. This method should be implemented
for all the handlers. This sends a string to their output media.
All level checking and formatting has already been done by
@code{accept-log}.")
(define-generic-with-docs open-log!
"@code{open-log! handler}. Tells the @code{handler} to open its log. Handlers for which
an open operation doesn't make sense can choose not to implement this method.
The default implementation just returns @code{#t}.")
;; provide do-nothing open for handlers that don't care about it
(define-method (open-log! (lh ))
#t)
(define-generic-with-docs close-log!
"@code{open-log! handler}. Tells the @code{handler} to close its
log. Handlers for which a close operation doesn't make sense can
choose not to implement this method. The default implementation
just returns @code{#t}.")
;; provide do-nothing close for handlers that don't care about it
(define-method (close-log! (lh ))
#t)
(define-generic-with-docs flush-log
"@code{flush-log handler}. Tells the @code{handler} to output
any log statements it may have buffered up. Handlers for which a
flush operation doesn't make sense can choose not to implement
this method. The default implementation just returns
@code{#t}.")
;; provide do-nothing flush for handlers that don't care about it
(define-method (flush-log (lh ))
#t)
;; if called with no args, pass to the default logger...
(define-method (flush-log)
(if default-logger
(flush-log default-logger)))
;; if called on a logger, pass the call to all the handlers...
(define-method (flush-log (lgr ))
(for-each (lambda (handler)
(flush-log handler))
(handlers lgr)))
(define-method (flush-log!)
(if default-logger
(flush-log! default-logger)))
(define-method (open-log! (lgr ))
(for-each (lambda (handler)
(open-log! handler))
(handlers lgr)))
(define-method (open-log!)
(if default-logger
(open-log! default-logger)))
(define-method (close-log! (lgr ))
(for-each (lambda (handler)
(close-log! handler))
(handlers lgr)))
(define-method (close-log!)
(if default-logger
(close-log! default-logger)))
;; ----------------------------------------------------------------------
;; These functions work on both and .
;; I could make them methods, but the contents would just be duplicated
;; Making them methods would allow people to make subclasses that altered
;; the log level behavior, I guess...
;; ----------------------------------------------------------------------
(define (enable-log-level! lgr lvl)
"Enables a specific logging level given by the symbol @var{lvl},
such that messages at that level will be sent to the log
handlers. @var{lgr} can be of type @code{} or
@code{}.
Note that any levels that are neither enabled or disabled are treated
as enabled by the logging system. This is so that misspelt level
names do not cause a logging blackout."
(hashq-set! (levels lgr) lvl #t))
(define (disable-log-level! lgr lvl)
"Disables a specific logging level, such that messages at that
level will not be sent to the log handlers. @var{lgr} can be of
type @code{} or @code{}.
Note that any levels that are neither enabled or disabled are treated
as enabled by the logging system. This is so that misspelt level
names do not cause a logging blackout."
(hashq-set! (levels lgr) lvl #f))
(define (level-enabled? lgr lvl)
;; defaults to #t so that if you misspell the log level you get your log
(hashq-ref (levels lgr) lvl #t))
;;; arch-tag: b90591f5-553e-4967-8f6e-83ab9a727a35
guile-lib-0.2.6.1/src/Makefile.am 0000664 0000764 0000764 00000005454 13314065643 013322 0000000 0000000
####
#### Copyright (C) 2016 - 2018 David Pirotte
#### David Pirotte
#### This file is part of Guile-Lib.
#### Guile-Lib is free software: you can redistribute it, as a whole,
#### and/or modify it under the terms of the GNU General Public
#### License as published by the Free Software Foundation, either
#### version 3 of the License, or (at your option) any later version.
#### Each Guile-Lib module contained in Guile-Lib has its own copying
#### conditions, specified in the comments at the beginning of the
#### module's source file.
#### Guile-Lib is distributed in the hope that it will be useful, but
#### WITHOUT ANY WARRANTY; without even the implied warranty of
#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#### General Public License for more details.
#### You should have received a copy of the GNU General Public License
#### along with Guile-Lib. If not, see
#### .
####
include $(top_srcdir)/am/guile.mk
SXML_FILES = \
sxml/apply-templates.scm \
sxml/fold.scm \
sxml/simple.scm \
sxml/ssax/input-parse.scm \
sxml/ssax.scm \
sxml/transform.scm \
sxml/unicode.scm \
sxml/upstream/SSAX-expanded.scm \
sxml/upstream/SSAX.scm \
sxml/upstream/SXML-tree-trans.scm \
sxml/upstream/SXPath-old.scm \
sxml/upstream/input-parse.scm \
sxml/upstream/assert.scm \
sxml/xpath.scm \
sxml/ssax-simple.scm
# Note, texinfo/nodal-tree.scm is not in guile 2.0, so it is in SOURCES
# below.
TEXINFO_FILES = \
texinfo/docbook.scm \
texinfo/html.scm \
texinfo/indexing.scm \
texinfo/plain-text.scm \
texinfo/reflection.scm \
texinfo/serialize.scm \
texinfo.scm
STATPROF_FILES = \
statprof.scm
# Guile-Lib depends on Guile 2.0, and Guile 2.0 already comes with the
# `(sxml ...)' and `(texinfo ...)' modules unmodified. We don't need
# the following variables anymore, also removed from the SOURCES
# definition below.
# SXML_SOURCES =
# TEXINFO_SOURCES =
# STATPROF_SOURCES =
SOURCES = \
apicheck.scm \
compat/guile-2.scm \
config/load.scm \
container/delay-tree.scm \
container/nodal-tree.scm \
container/async-queue.scm \
debugging/assert.scm \
debugging/time.scm \
graph/topological-sort.scm \
htmlprag.scm \
io/string.scm \
logging/logger.scm \
logging/port-log.scm \
logging/rotating-log.scm \
math/minima.scm \
math/primes.scm \
match-bind.scm \
md5.scm \
os/process.scm \
scheme/documentation.scm \
scheme/kwargs.scm \
search/basic.scm \
string/completion.scm \
string/soundex.scm \
string/transform.scm \
string/wrap.scm \
term/ansi-color.scm \
texinfo/nodal-tree.scm \
text/parse-lalr.scm \
unit-test.scm
EXTRA_DIST = \
$(SOURCES) \
$(NOCOMP_SOURCES)
CLEANFILES = $(GOBJECTS)
printenv:
printf '$(moddir)\n$(godir)\n'
guile-lib-0.2.6.1/src/math/ 0000775 0000764 0000764 00000000000 13320656103 012261 5 0000000 0000000 guile-lib-0.2.6.1/src/math/primes.scm 0000664 0000764 0000764 00000023067 13314016560 014214 0000000 0000000 ;; (math primes) -- factorization, prime test, and generation
;; Copyright (C) 1991, 1992, 1993, 1998, 2010 Aubrey Jaffer
;; Permission to copy this software, to modify it, to redistribute it,
;; to distribute modified versions, and to use it for any purpose is
;; granted, subject to the following restrictions and understandings.
;;
;; 1. Any copy made of this software must include this copyright notice
;; in full.
;;
;; 2. I have made no warranty or representation that the operation of
;; this software will be error-free, and I am under no obligation to
;; provide any services, by way of maintenance, update, or otherwise.
;;
;; 3. In conjunction with products arising from the use of this
;; material, there shall be no use of my name in any advertising,
;; promotional, or sales literature without prior written consent in
;; each case.
;;; Commentary:
;;
;; @cindex prime number
;; @cindex numbers, prime
;; @cindex numbers, prime factors of
;; @cindex prime factors
;; @cindex factors, prime
;; This module defines functions related to prime numbers, and prime factorization.
;;
;;; Code:
(define-module (math primes)
#:use-module (scheme documentation)
#:export (prime:trials
prime?
prime>
primes>
prime<
primes<
factor))
;;@body
;;@0 is the random-state (@pxref{Random Numbers}) used by these
;;procedures. If you call these procedures from more than one thread
;;(or from interrupt), @code{random} may complain about reentrant
;;calls.
(define prime:prngs
(seed->random-state "repeatable seed for primes"))
;;@emph{Note:} The prime test and generation procedures implement (or
;;use) the Solovay-Strassen primality test. See
;;
;;@itemize @bullet
;;@item Robert Solovay and Volker Strassen,
;;@cite{A Fast Monte-Carlo Test for Primality},
;;SIAM Journal on Computing, 1977, pp 84-85.
;;@end itemize
;;; Solovay-Strassen Prime Test
;;; if n is prime, then J(a,n) is congruent mod n to a**((n-1)/2)
;;; (modulo p 16) is because we care only about the low order bits.
;;; The odd? tests are inline of (expt -1 ...)
;;@args p q
;;Returns the value (+1, @minus{}1, or 0) of the Jacobi-Symbol of
;;exact non-negative integer @1 and exact positive odd integer @2.
(define (prime:jacobi-symbol p q)
(cond ((zero? p) 0)
((= 1 p) 1)
((odd? p)
(if (odd? (quotient (* (- (modulo p 16) 1) (- q 1)) 4))
(- (prime:jacobi-symbol (modulo q p) p))
(prime:jacobi-symbol (modulo q p) p)))
(else
(let ((qq (modulo q 16)))
(if (odd? (quotient (- (* qq qq) 1) 8))
(- (prime:jacobi-symbol (quotient p 2) q))
(prime:jacobi-symbol (quotient p 2) q))))))
(define-with-docs prime:trials
"This is the maximum number of iterations of Solovay-Strassen that will
be done to test a number for primality. The chance of error (a composite
being labelled prime) is @code{(expt 2 (- prime:trials))}."
30)
;;; checks if n is prime. Returns #f if not prime. #t if (probably) prime.
;;; probability of a mistake = (expt 2 (- prime:trials))
;;; choosing prime:trials=30 should be enough
(define (Solovay-Strassen-prime? n)
(do ((i prime:trials (- i 1))
(a (+ 2 (random (- n 2) prime:prngs))
(+ 2 (random (- n 2) prime:prngs))))
((not (and (positive? i)
(= (gcd a n) 1)
(= (modulo (prime:jacobi-symbol a n) n)
(modulo-expt a (quotient (- n 1) 2) n))))
(if (positive? i) #f #t))))
;;; prime:products are products of small primes.
;;; was (comlist:notevery (lambda (prd) (= 1 (gcd n prd))) comps))
(define (primes-gcd? n comps)
(not (let mapf ((lst comps))
(or (null? lst) (and (= 1 (gcd n (car lst))) (mapf (cdr lst)))))))
(define prime:prime-sqr 121)
(define prime:products '(105))
(define prime:sieve #*00110101000)
(letrec ((lp (lambda (comp comps primes nexp)
(cond ((< comp (quotient most-positive-fixnum nexp))
(let ((ncomp (* nexp comp)))
(lp ncomp comps
(cons nexp primes)
(next-prime nexp (cons ncomp comps)))))
((< (quotient comp nexp) (* nexp nexp))
(set! prime:prime-sqr (* nexp nexp))
(set! prime:sieve (make-bitvector nexp #f))
(for-each (lambda (prime)
(bitvector-set! prime:sieve prime #t))
primes)
(set! prime:products (reverse (cons comp comps))))
(else
(lp nexp (cons comp comps)
(cons nexp primes)
(next-prime nexp (cons comp comps)))))))
(next-prime (lambda (nexp comps)
(set! comps (reverse comps))
(do ((nexp (+ 2 nexp) (+ 2 nexp)))
((not (primes-gcd? nexp comps)) nexp)))))
(lp 3 '() '(2 3) 5))
(define (prime? n)
"Returns @code{#f} if @var{n} is composite, and @code{t} if it is prime.
There is a slight chance, @code{(expt 2 (- prime:trials))}, that a
composite will return @code{#t}."
(set! n (abs n))
(cond ((< n (bitvector-length prime:sieve)) (bitvector-ref prime:sieve n))
((even? n) #f)
((primes-gcd? n prime:products) #f)
((< n prime:prime-sqr) #t)
(else (Solovay-Strassen-prime? n))))
(define (prime< start)
"Return the first prime number less than @var{start}. It doesn't matter
if @var{start} is prime or composite. If no primes are less than @var{start},
@code{#f} will be returned."
(do ((nbr (+ -1 start) (+ -1 nbr)))
((or (negative? nbr) (prime? nbr))
(if (negative? nbr) #f nbr))))
(define (primes< start count)
"Returns a list of the first @var{count} prime numbers less than
@var{start}. If there are fewer than @var{count} prime numbers
less than @var{start}, then the returned list will have fewer than
@var{start} elements."
(do ((cnt (+ -2 count) (+ -1 cnt))
(lst '() (cons prime lst))
(prime (prime< start) (prime< prime)))
((or (not prime) (negative? cnt))
(if prime (cons prime lst) lst))))
(define (prime> start)
"Return the first prime number greater than @var{start}. It doesn't matter
if @var{start} is prime or composite."
(do ((nbr (+ 1 start) (+ 1 nbr)))
((prime? nbr) nbr)))
(define (primes> start count)
"Returns a list of the first @var{count} prime numbers greater than @var{start}."
(set! start (max 0 start))
(do ((cnt (+ -2 count) (+ -1 cnt))
(lst '() (cons prime lst))
(prime (prime> start) (prime> prime)))
((negative? cnt)
(reverse (cons prime lst)))))
;;;;Lankinen's recursive factoring algorithm:
;From: ld231782@longs.LANCE.ColoState.EDU (L. Detweiler)
; | undefined if n<0,
; | (u,v) if n=0,
;Let f(u,v,b,n) := | [otherwise]
; | f(u+b,v,2b,(n-v)/2) or f(u,v+b,2b,(n-u)/2) if n odd
; | f(u,v,2b,n/2) or f(u+b,v+b,2b,(n-u-v-b)/2) if n even
;Thm: f(1,1,2,(m-1)/2) = (p,q) iff pq=m for odd m.
;It may be illuminating to consider the relation of the Lankinen function in
;a `computational hierarchy' of other factoring functions.* Assumptions are
;made herein on the basis of conventional digital (binary) computers. Also,
;complexity orders are given for the worst case scenarios (when the number to
;be factored is prime). However, all algorithms would probably perform to
;the same constant multiple of the given orders for complete composite
;factorizations.
;Thm: Eratosthenes' Sieve is very roughtly O(ln(n)/n) in time and
; O(n*log2(n)) in space.
;Pf: It works with all prime factors less than n (about ln(n)/n by the prime
; number thm), requiring an array of size proportional to n with log2(n)
; space for each entry.
;Thm: `Odd factors' is O((sqrt(n)/2)*log2(n)) in time and O(log2(n)) in
; space.
;Pf: It tests all odd factors less than the square root of n (about
; sqrt(n)/2), with log2(n) time for each division. It requires only
; log2(n) space for the number and divisors.
;Thm: Lankinen's algorithm is O(sqrt(n)/2) in time and O((sqrt(n)/2)*log2(n))
; in space.
;Pf: The algorithm is easily modified to seach only for factors p